Docker Deployment
Deploy Deckyard using Docker and Docker Compose for easy setup and management.
Prerequisites
Section titled “Prerequisites”- Docker - Version 20.10 or later
- Docker Compose - Version 2.0 or later
- Domain name - For HTTPS (production)
- DNS configured - Pointing to your server
Quick Start
Section titled “Quick Start”1. Clone the Repository
Section titled “1. Clone the Repository”git clone https://github.com/jaapstronks/deckyard.gitcd deckyard2. Create Environment File
Section titled “2. Create Environment File”Copy the example environment file:
cp .env.example .envEdit .env with your configuration:
# Required unless you set AUTH_ENABLED=false (at least 32 characters)AUTH_SECRET=your-random-secret-key-hereAUTH_ADMIN_EMAIL=admin@yourdomain.com
# Domain (for Caddy HTTPS)DOMAIN=slides.yourdomain.comLETSENCRYPT_EMAIL=admin@yourdomain.com
# Optional: EmailBREVO_API_KEY=your-brevo-api-keyBREVO_SENDER_EMAIL=slides@yourdomain.comBREVO_SENDER_NAME=Deckyard
# Optional: AI featuresOPENAI_API=sk-...
# Optional: Media storageIMAGEKIT_PUBLIC_KEY=...IMAGEKIT_PRIVATE_KEY=...IMAGEKIT_URL_ENDPOINT=...The server refuses to start without an AUTH_SECRET of at least 32 characters unless you opt out of authentication with AUTH_ENABLED=false (see Authentication). On the first start, with an empty user table, the server creates AUTH_ADMIN_EMAIL as the first admin, who signs in with a magic link or through SSO (see Your first admin). The database needs no settings: the stack brings its own.
3. Start the Stack
Section titled “3. Start the Stack”docker compose up -dThis starts:
- app - The Deckyard application on port 4177 (reachable only on the compose network)
- postgres - PostgreSQL 16, the database; migrations run automatically when the app container starts
- caddy - Reverse proxy with automatic HTTPS
4. Access Your Instance
Section titled “4. Access Your Instance”Open https://your-domain.com in your browser.
Docker Compose Configuration
Section titled “Docker Compose Configuration”Default Configuration
Section titled “Default Configuration”docker-compose.yml in the repository is the production topology: app behind caddy on ports 80 and 443, with postgres next to it. The app only exposes port 4177 on the compose network, so a bare docker compose up does not publish http://localhost:4177. For a local run without a domain, add the local overlay, which publishes 4177 (and the database on 5433) and leaves Caddy out:
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --buildEvery DATABASE_* value in the compose file can be overridden from .env; Database Configuration explains the defaults and how to use a managed database instead.
Volumes
Section titled “Volumes”| Volume | Purpose |
|---|---|
pg_data | The PostgreSQL database: presentations, users, settings and everything else except media |
./server/uploads | Uploaded images and files |
./server/data | Deck-thumbnail cache, plus files left by an install from before PostgreSQL |
caddy_data | SSL certificates and Caddy state |
caddy_config | Caddy configuration cache |
Reverse Proxy (Caddy)
Section titled “Reverse Proxy (Caddy)”Default Caddyfile
Section titled “Default Caddyfile”{ email {$LETSENCRYPT_EMAIL}}
{$DOMAIN} { encode gzip reverse_proxy app:4177}Caddy automatically:
- Obtains SSL certificates from Let’s Encrypt
- Renews certificates before expiry
- Redirects HTTP to HTTPS
- Enables gzip compression
Environment Variables
Section titled “Environment Variables”Set these in your .env file:
DOMAIN=slides.yourdomain.comLETSENCRYPT_EMAIL=admin@yourdomain.comMultiple Domains
Section titled “Multiple Domains”To serve multiple domains:
{ email {$LETSENCRYPT_EMAIL}}
slides.example.com, presentations.example.com { encode gzip reverse_proxy app:4177}Dockerfile
Section titled “Dockerfile”The included Dockerfile builds on node:22-alpine and adds:
- Chromium (plus an emoji font) - for server-side PNG, PDF and PPTX exports
- A non-root user - the app runs as the image’s
nodeuser - An entrypoint - applies pending database migrations, then starts the server
Production Configuration
Section titled “Production Configuration”Resource Limits
Section titled “Resource Limits”Add resource limits in docker-compose.yml:
services: app: # ... other config deploy: resources: limits: cpus: '2' memory: 2G reservations: cpus: '0.5' memory: 512MHealth Checks
Section titled “Health Checks”The image has its own HEALTHCHECK on GET /health (no login, no database), so Docker, Coolify or another orchestrator knows when the container serves without any configuration. docker compose ps shows the result. Override it in docker-compose.yml only if you need other intervals.
Check the Configuration
Section titled “Check the Configuration”/health says the process is up; npm run doctor says it is configured right. It checks the installation without starting the server: auth secret, public URL, database and migrations, SSO and the provider’s discovery document, themes, Chrome for exports, uploads and mail. One line per check, and for anything red, the fix.
docker compose exec app node scripts/doctor.js # in the running containernpm run doctor # in a checkout, reads .envnpm run doctor -- --json # the same findings as JSONIt exits with 1 when a check failed, so a deploy step can gate on it; warnings do not fail. Run it after editing .env, before the first user, and after every update. It writes nothing lasting: the uploads check writes two probe files and deletes them again.
Logging
Section titled “Logging”Configure log rotation:
services: app: # ... other config logging: driver: "json-file" options: max-size: "10m" max-file: "3"External Database
Section titled “External Database”The stack’s own postgres service is the default. To use a managed or existing PostgreSQL instead, set its connection in .env, for example:
DATABASE_URL=postgres://user:pass@your-db-host:5432/deckyardDo not set DATABASE_HOST=localhost here: inside the container that is the app itself, and the entrypoint refuses to start. See Database Configuration for SSL and pool settings.
Updating
Section titled “Updating”Pull Latest Changes
Section titled “Pull Latest Changes”git pull origin maindocker compose builddocker compose up -dPending migrations run when the new app container starts. Then run docker compose exec app node scripts/doctor.js to confirm the configuration still passes; read the release notes for renamed settings first.
Rebuild Only the App
Section titled “Rebuild Only the App”To rebuild and restart the app without touching the database and Caddy containers:
docker compose up -d --no-deps --build appThe app is briefly unavailable while its container is replaced.
Backup
Section titled “Backup”Back up the database and the media together, the media right after the dump:
docker compose exec -T postgres sh -c \ 'pg_dump -Fc --no-owner --no-acl -U "$POSTGRES_USER" "$POSTGRES_DB"' \ > deckyard-$(date +%F).dumptar czf deckyard-uploads-$(date +%F).tgz server/uploads server/data/private-mediaserver/data/private-media holds uploaded fonts, which the database refers to; the rest of server/data is a thumbnail cache. How to restore, and how to rehearse a restore, is on Back Up and Restore.
Troubleshooting
Section titled “Troubleshooting”View Logs
Section titled “View Logs”# All servicesdocker compose logs -f
# Specific servicedocker compose logs -f appdocker compose logs -f caddyContainer Status
Section titled “Container Status”docker compose psShell Access
Section titled “Shell Access”docker compose exec app shSSL Certificate Issues
Section titled “SSL Certificate Issues”If Caddy fails to get certificates:
- Check domain DNS is pointing to your server
- Verify ports 80 and 443 are open
- Check Caddy logs:
docker compose logs caddy
Memory Issues
Section titled “Memory Issues”If the app crashes due to memory:
- Increase memory limits in docker-compose.yml
- Use external media storage
Development Mode
Section titled “Development Mode”For a local run with Docker, use the local overlay described under Default Configuration:
docker compose -f docker-compose.yml -f docker-compose.local.yml up -d --buildOr run without Docker for faster iteration.