Documentation Index
Fetch the complete documentation index at: https://docs.techulus.cloud/llms.txt
Use this file to discover all available pages before exploring further.
Prerequisites
- Docker and Docker Compose
- A domain name with DNS configured
- Ports 80 and 443 available
You need three DNS records pointing to your server:
| Record | Purpose |
|---|
your-domain.com | Control plane web UI |
registry.your-domain.com | Container image registry |
logs.your-domain.com | Log aggregation (Victoria Logs) |
Quick Start
Run the automated install script on a fresh server:
curl -fsSL https://raw.githubusercontent.com/techulus/cloud/main/deployment/install.sh | bash
The script detects your OS, installs Docker, walks you through DNS and environment configuration, and starts all services.
Manual Setup
Clone the repository and configure your environment:
cd deployment
cp .env.example .env
Edit .env with your values (see below), then start the stack:
docker compose -f compose.production.yml up -d --pull always --remove-orphans
To use the bundled PostgreSQL instead of an external database:
docker compose -f compose.postgres.yml up -d --pull always --remove-orphans
Production hosts should also cap Docker container logs. The installer creates
/etc/docker/daemon.json with json-file rotation on fresh hosts. If Docker is
already configured, keep your existing daemon settings and add equivalent log
rotation manually.
The Compose files include container health checks for visibility. Plain Docker
Compose reports unhealthy containers but does not restart them automatically, so
use the common commands below when investigating a self-hosted service.
Environment Variables
Required
| Variable | Description |
|---|
ROOT_DOMAIN | Your domain (e.g., cloud.example.com) |
ACME_EMAIL | Email for Let’s Encrypt certificates |
DATABASE_URL | PostgreSQL connection string (e.g., postgres://user:pass@postgres:5432/techulus) |
BETTER_AUTH_SECRET | Secret key for authentication |
ENCRYPTION_KEY | 32 bytes as a 64-character hex string |
Victoria Logs
| Variable | Description |
|---|
VL_USERNAME | Logs service username |
VL_PASSWORD | Logs service password |
VL_RETENTION | Log retention period (default: 7d) |
Registry
| Variable | Description |
|---|
REGISTRY_USERNAME | Registry username for agents |
REGISTRY_PASSWORD | Registry password for agents |
REGISTRY_HTTP_SECRET | Internal registry secret |
Inngest
| Variable | Description |
|---|
INNGEST_SIGNING_KEY | Request verification key (prefix with signkey-prod-) |
INNGEST_EVENT_KEY | Event API key |
Control Plane Replicas
| Variable | Description |
|---|
WEB_REPLICAS | Number of control plane web containers to run (default: 1) |
When WEB_REPLICAS is greater than 1, Traefik discovers the replicated web
containers through Docker and load balances requests for <ROOT_DOMAIN> across
them. Startup schema sync still runs from each web container, so simultaneous
replica starts may run drizzle-kit push concurrently during upgrades.
GitHub Integration (Optional)
| Variable | Description |
|---|
GITHUB_APP_ID | GitHub App ID |
GITHUB_APP_PRIVATE_KEY | GitHub App private key (base64-encoded) |
GITHUB_WEBHOOK_SECRET | Webhook secret |
Generating Secrets
# Encryption key (64 hex characters)
openssl rand -hex 32
# Auth secret
openssl rand -hex 32
# Inngest signing key
echo "signkey-prod-$(openssl rand -hex 32)"
# Inngest event key
openssl rand -hex 16
Services
Once running, the following services are available:
| Service | Endpoint |
|---|
| Web | https://<ROOT_DOMAIN> |
| Registry | https://registry.<ROOT_DOMAIN> |
| Logs | https://logs.<ROOT_DOMAIN> |
| PostgreSQL | Internal only |
| Inngest | Internal only |
Traefik handles TLS termination and automatic certificate renewal via Let’s Encrypt.
Database Migrations
The schema is synced automatically on container startup via drizzle-kit push. Non-destructive changes (adding tables, columns, indexes) are applied automatically. Destructive changes like dropping columns require manual intervention.
Common Commands
# Check service status
docker compose -f compose.production.yml ps
# View logs
docker compose -f compose.production.yml logs -f
# Stop all services
docker compose -f compose.production.yml down --remove-orphans
# Update to the configured image references
docker compose -f compose.production.yml up -d --pull always --remove-orphans
Use versioned or digest-pinned image references for production updates when
possible. Mutable tags such as latest and tip are convenient, but they can
move between pulls.