docs Configuration

Configuration

Instance-level configuration is done through environment variables in .env.

Where configuration lives

A production install reads /opt/deploykit/.env. The installer generates it for you; for a manual install, start from .env.production. docker-compose.prod.yml builds some variables itself — DATABASE_URL, REDIS_URL, WEB_URL, NODE_ENV=production, API_PORT=4000 and COMPOSE_ROOT — and those values win over anything you put in .env. Every other variable is read from the .env file that is copied into the API image when it is built.

Applying changes: after editing .env, rebuild and recreate the containers — cd /opt/deploykit && docker compose -f docker-compose.prod.yml up -d --build. A plain deploykit restart does not pick up the new values.

Required Environment Variables

bash
# Panel domain and Let's Encrypt email (production)
DOMAIN=panel.yourdomain.com
ACME_EMAIL=you@example.com

# PostgreSQL — production builds DATABASE_URL from these
DB_USER=deploykit
DB_PASSWORD=<openssl rand -hex 24>
DB_NAME=deploykit

# Redis password — required in production since v0.6.0.
# Hex only: it is embedded in redis://:<password>@redis:6379
REDIS_PASSWORD=<openssl rand -hex 32>

# JWT signing secrets (openssl rand -base64 32)
JWT_SECRET=<your-jwt-secret>
JWT_REFRESH_SECRET=<your-refresh-secret>

# AES-256-GCM key for secrets at rest (openssl rand -hex 32)
ENCRYPTION_KEY=<64-character-hex-string>

# Development / external services only — production sets these itself
DATABASE_URL=postgresql://user:password@localhost:5432/deploykit
REDIS_URL=redis://localhost:6379

With NODE_ENV=production the API refuses to start if JWT_SECRET, JWT_REFRESH_SECRET or ENCRYPTION_KEY is missing, shorter than 32 characters, or still contains a CHANGE_ME placeholder; in development it only warns.

Warning: never change ENCRYPTION_KEY once data exists. Env vars, source tokens, SSH keys and other stored secrets are encrypted with it, and become unreadable under a new key. Keep a copy of your .env somewhere safe.

Public URL (WEB_URL)

WEB_URL is the public URL of your instance. CORS and Socket.IO origins, the links DeployKit puts on GitHub commit statuses, and the webhook and redirect URLs registered in a GitHub App are all built from it. DeployKit never derives it from the request's Host header, which a client controls.

In production you do not set it: the Compose file derives it as https://${DOMAIN}, because Traefik serves the dashboard and the API on the same host. In development it defaults to http://localhost:5173; to register a GitHub App from a dev machine, point it at a public tunnel (cloudflared, ngrok) — the App's webhook URL must be reachable by GitHub.

Changing DOMAIN: a GitHub App keeps the webhook URL it was registered with. If you change DOMAIN after registering one, disconnect and register the App again (or edit its URLs on GitHub), or pushes stop triggering deploys.

Optional Environment Variables

bash
# Default HMAC secret for GitHub/GitLab webhooks and the generic webhook
# (X-Webhook-Token). Without it, GitLab webhooks — and GitHub webhooks for
# apps with no per-app secret and no GitHub App — are rejected.
# Optional for GitHub once a GitHub App is registered.
WEBHOOK_SECRET=<openssl rand -base64 32>

# Set to "true" ONLY when every request reaches the API through a trusted
# proxy that sets X-Forwarded-For. Otherwise the header is ignored, so
# clients cannot spoof their IP in rate limits and audit logs. Default: false
TRUST_PROXY=false

# "production" enables HSTS and the fail-fast secret check (set by the prod compose)
NODE_ENV=development

# API port (default 3001; production fixes it to 4000 behind Traefik)
API_PORT=3001

# Docker socket path — auto-detected; override only if non-standard
DOCKER_SOCKET=/var/run/docker.sock

# Retention
AUDIT_RETENTION_DAYS=90             # audit log entries (pruned daily at 04:00)
LOG_RETENTION_DAYS=7                # persistent container logs
METRICS_RAW_RETENTION_HOURS=48      # 1-minute metric samples
METRICS_HISTORY_RETENTION_DAYS=90   # 1-hour metric rollups
LOG_BUFFER_MAX=5000                 # log lines buffered before writing; excess is dropped

Compose Stacks & Templates

bash
# Where stack directories live. Must be bind-mounted from the host at the
# SAME absolute path (production fixes it to /var/lib/deploykit/compose).
COMPOSE_ROOT=/var/lib/deploykit/compose

# Wildcard domain pointing at this host, used for template hostnames when
# you don't supply one (*.apps.yourdomain.com). The only option that can
# get a real Let's Encrypt certificate.
TEMPLATES_BASE_DOMAIN=apps.yourdomain.com

# Fallback without a wildcard domain: this host's public IPv4. Templates get
# <name>-<rand>.<ip>.sslip.io hostnames — no DNS setup, but plain HTTP only.
PUBLIC_IP=203.0.113.10

# Template registry. Point it at a fork or private mirror to publish your own
# blueprints. If unreachable, the catalogue bundled in the image is used.
TEMPLATES_REGISTRY_URL=https://raw.githubusercontent.com/deploykithq/deploykit-templates/master

Game Servers

bash
# Host port range DeployKit suggests free ports from when a game's default
# port is taken. Players connect straight to these ports — open them in your
# provider's firewall.
GAME_PORT_RANGE=20000-40000

# Largest file the game server Files tab accepts as an upload, in bytes (256 MiB)
GAME_UPLOAD_MAX_BYTES=268435456

CurseForge and Steam API keys are not environment variables: an admin sets them in Settings → Game mods, where they are stored encrypted. Likewise, the GitHub App is registered from Settings → GitHub App — the old GITHUB_CLIENT_ID / GITHUB_CLIENT_SECRET variables are no longer used.

Vulnerability Scanning (Trivy)

bash
# Scan built images by default. Only the exact value "true" enables it;
# unset means off. Each application can override this in its General tab.
SCAN_ENABLED=true

TRIVY_IMAGE=aquasec/trivy:latest            # scanner image
TRIVY_CACHE_VOLUME=deploykit-trivy-cache    # volume caching the vulnerability DB
TRIVY_TIMEOUT_SECONDS=300                   # per-scan timeout

Scans are advisory — results are recorded on the deployment and never block it — and run for applications on the local server only.

Security Configuration

DeployKit includes built-in security features that are configured automatically:

  • Rate Limiting — Global: 200 req/min per IP on /trpc and /api; login and register: 10 req/min; webhooks: 120 req/min
  • Request size — 1 MiB per request body, except the configuration import (4 MiB)
  • Encryption — Env vars, tokens, SSH keys and integration credentials encrypted with AES-256-GCM
  • Security Headers — X-Content-Type-Options, X-Frame-Options, Referrer-Policy, a strict CSP, and HSTS in production
  • Secret Redaction — Build logs automatically scrub tokens, passwords, and keys
  • CORS — Only WEB_URL is allowed, for both the API and Socket.IO
  • Isolation — Redis requires a password, Traefik reads Docker through a read-only proxy, and every production container runs with no-new-privileges

Development Setup

bash
# Requires Node.js 20+, pnpm 9+ and Docker
pnpm install
cp .env.example .env   # fill in the secrets

# Start PostgreSQL, Redis and Traefik
docker compose up -d

# Apply migrations, then start API + Web in dev mode
pnpm db:migrate
pnpm dev

# Or start individually
pnpm dev:api   # API on :3001
pnpm dev:web   # Web on :5173

# Other commands
pnpm db:studio    # Open Drizzle Studio GUI
pnpm build        # Production build
pnpm lint         # Type check all packages
pnpm clean        # Remove dist/ directories

Do not run pnpm db:generate or drizzle-kit push: the generator compares against an outdated baseline and produces a broken migration. New migrations are written by hand as SQL.