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). 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”Add health checks for reliability:
services: app: # ... other config healthcheck: test: ["CMD", "wget", "-q", "--spider", "http://localhost:4177/health"] interval: 30s timeout: 10s retries: 3 start_period: 40sLogging
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.
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 uploads together:
docker compose exec postgres sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup-$(date +%Y%m%d).sqltar -czvf uploads-$(date +%Y%m%d).tar.gz ./server/uploadsWith a managed database, back that up through your provider instead. server/data holds only a cache and needs no backup once any old file-based data has been imported (Upgrading from file-based storage).
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.