Docker Deployment
Deploy Mitch‑Risk with Docker Compose for a self-hosted, single-container setup.
Quick Deploy (pre-built image)
No build tools required — just Docker. Pull the pre-built image from GitHub Container Registry:
curl -O https://raw.githubusercontent.com/mitchelljfranklin/mitch-risk/master/docker-compose.pull.ymlCreate a .env file with your secrets:
AUTH_SECRET=<your-secret>
APP_ENCRYPTION_KEY=<your-key-min-32-chars>
CRON_SECRET=<your-secret>
APP_URL=http://localhost:3000docker compose -f docker-compose.pull.yml up -dThe stack includes two containers:
- app — Next.js application server (port 3000)
- db — PostgreSQL database (internal network, port 5432)
Portainer
A ready-to-customize template is available for Portainer's stack deployment:
curl -O https://raw.githubusercontent.com/mitchelljfranklin/mitch-risk/master/docker-compose.portainer.ymlPaste the contents into Portainer's stack editor, replace the CHANGE_ME placeholders with your own secrets, and deploy.
Quick Deploy (build from source)
git clone https://github.com/mitchelljfranklin/mitch-risk.git
cd mitch-risk
cp .env.example .envEdit .env with your secrets, then:
docker compose up -dSecrets to Change
Change these before exposing to any network. The defaults are for local development only.
| Secret | Location | Purpose |
|---|---|---|
AUTH_SECRET | .env | JWT signing key for sessions |
APP_ENCRYPTION_KEY | .env | AES-256-GCM key for encrypting secrets at rest |
CRON_SECRET | .env | Secret for triggering cron jobs |
POSTGRES_PASSWORD | .env / docker-compose.yml | Database password |
Container Security
- Non-root user: The application container runs as the
nodeuser (not root) for defense in depth. - Resource limits: Both the app and database containers have configurable resource limits:
APP_CPU_LIMIT(default: 1.0),APP_MEMORY_LIMIT(default: 1g)DB_CPU_LIMIT(default: 0.5),DB_MEMORY_LIMIT(default: 512m)- Set these in
.envordocker-compose.ymlto tune or disable limits for your deployment.
Generate Strong Secrets
# On Linux/macOS
openssl rand -hex 32 # For AUTH_SECRET, APP_ENCRYPTION_KEY, CRON_SECRET# On Windows PowerShell
-join ((48..57) + (65..90) + (97..122) | Get-Random -Count 64 | ForEach-Object { [char]$_ })First-Run Setup
- After
docker compose up -d, openhttp://localhost:3000/setup. - Create your first admin account.
- The
/setuppage is only available when zero users exist.
The production container requires
CRON_SECRETto boot. Set it in.envbefore starting.
Volumes
| Volume | Purpose | Backup? |
|---|---|---|
db_data | PostgreSQL data directory | Yes |
evidence_data | Uploaded files (evidence, logos, attachments) | Yes |
Do not run
docker compose down -vunless you intend to wipe all data. The-vflag deletes both volumes.
Environment Variables in Compose
The expected .env file for Docker Compose:
DATABASE_URL=postgresql://mitch:mitch@db:5432/mitch_risk?schema=public
AUTH_SECRET=<your-secret>
APP_ENCRYPTION_KEY=<your-key-min-32-chars>
CRON_SECRET=<your-secret>
APP_URL=https://risk.example.com
EVIDENCE_STORAGE_PATH=/app/.storage/evidence
TRUSTED_PROXY_COUNT=1External Database
You can use an existing PostgreSQL instance (on-premises, cloud-hosted, or a separate Docker container) instead of the bundled db service.
Option A: App container only (external DB)
Use docker-compose.external-db.yml and set a single env var:
# Point to your existing PostgreSQL database
export DATABASE_URL="postgresql://user:pass@host:5432/mitch_risk?schema=public"
# Set the same secrets as the all-in-one deploy
export AUTH_SECRET=<your-secret>
export APP_ENCRYPTION_KEY=<your-key>
export CRON_SECRET=<your-secret>
docker compose -f docker-compose.external-db.yml up -dThe app auto-applies migrations and seeds on first start.
Option B: Standalone Postgres container
Use docker-compose.db-only.yml to run just the database, then connect the app container to it via DATABASE_URL:
# Start the database container
docker compose -f docker-compose.db-only.yml up -d
# Then start the app with the external-db compose file
export DATABASE_URL="postgresql://mitch:mitch@localhost:5432/mitch_risk?schema=public"
docker compose -f docker-compose.external-db.yml up -dOption C: Manual DB setup
If you prefer to run migrations and seed manually before deploying the app container:
# From the project root, with DATABASE_URL set:
DATABASE_URL="postgresql://..." bash scripts/setup-db.shOr on Windows:
$env:DATABASE_URL = "postgresql://..."
.\scripts\setup-db.ps1Then deploy the app container with SKIP_SEED=true to prevent re-seeding:
export SKIP_SEED=true
docker compose -f docker-compose.external-db.yml up -dAzure Database for PostgreSQL
When using Azure PostgreSQL Flexible Server, append sslmode=require to the connection string:
DATABASE_URL="postgresql://user:pass@server.postgres.database.azure.com:5432/mitch_risk?schema=public&sslmode=require"See the full Azure Container Apps deployment guide.
Upgrading
Pre-built image
docker compose -f docker-compose.pull.yml pull
docker compose -f docker-compose.pull.yml down
docker compose -f docker-compose.pull.yml up -dBuild from source
git pull
docker compose down
docker compose build --no-cache
docker compose up -dMigrations run automatically on container start (prisma migrate deploy).
Multi-Container Considerations
The in-memory rate limiter is per-process — correct for single-container deployment. If you scale the app horizontally:
- Add a shared Redis instance for rate limiting
- Set
TRUSTED_PROXY_COUNTcorrectly for all proxy hops - Cron jobs should only run from one instance
Scheduled Tasks (Cron)
Schedule a system cron to hit the secured endpoint:
# Every 5 minutes
*/5 * * * * curl -H "x-cron-secret: $CRON_SECRET" http://localhost:3000/api/cron/runThis triggers: vendor reminders, overdue escalations, recurring assessment creation, certification/contract expiry notices, audit log pruning, email log pruning, and orphaned file sweep.
Rotating Encryption Keys
APP_ENCRYPTION_KEY is used to encrypt secrets stored in the database (SMTP password, SSO client secrets, cloud storage credentials). Changing this key is destructive — all encrypted secrets become permanently unreadable. There is no key versioning or re-encryption mechanism.
To rotate safely:
- Before changing
APP_ENCRYPTION_KEY, open Settings and note down every encrypted value (SMTP password, SSO client secrets, cloud credentials). They will be blanked when the new key is applied. - Change the key in
.envordocker-compose.yml. - Restart the container.
- Re-enter all secrets in Settings. They will be encrypted with the new key.
To recover after an accidental key change:
- Restore the previous
APP_ENCRYPTION_KEYvalue. - Restart the container — existing encrypted secrets will be readable again.
- Follow the safe rotation procedure above.
If the previous key has been permanently lost, all affected settings must be re-entered manually.