Artistry & Intelligence

Enter the Atelier

How-To Guide

Docker Production Deployment

Deploy to your production server with automatic HTTPS, performance optimization, and monitoring.

Meta-awareness

This is the service manual for the website you're reading right now. The site is deployed exactly as described here—Docker containers, caddy-docker-proxy, automatic HTTPS. You're looking at the result of following this guide.

Prerequisites

Before deploying to production, ensure:

  • Linux server with Docker (Ubuntu 22.04+ recommended)
  • Domain name pointing to server IP (A record)
  • Ports 80/443 open in firewall
  • caddy-docker-proxy running on server
  • SSH access to server (key-based auth recommended)

Verify prerequisites on server:

# Check Docker version
docker --version

# Check caddy network exists
docker network ls | grep caddy

# Check firewall (UFW example)
sudo ufw status

# Expected:
# 80/tcp    ALLOW
# 443/tcp   ALLOW

Step 1: Server setup

Connect to your server and prepare the environment:

terminal
# SSH into server
ssh user@your-server.com

# Create application directory
sudo mkdir -p /opt/installations
cd /opt/installations

# Clone repository
git clone https://github.com/your-org/your-installation.git
cd your-installation/website

Tip: Use /opt/installations/ for consistency across deployments. Adjust permissions as needed for your deployment user.

Step 2: Production environment

Create .env.production with production-specific settings:

.env.production
# Production mode
ATELIER_MODE=prod
NODE_ENV=production

# Disable development features
ENABLE_HMR=false
DEBUG=false

# Domain configuration
PUBLIC_DOMAIN=your-domain.com
COMPOSE_PROJECT_NAME=your-installation-prod

# Ambient effects
AMBIENT_EFFECTS_ENABLED=true
AMBIENT_INTENSITY=medium

# Performance
NODE_OPTIONS="--max-old-space-size=2048"

# Logging
LOG_LEVEL=warn

# Optional: Analytics, monitoring
# ANALYTICS_ID=your-analytics-id
# SENTRY_DSN=your-sentry-dsn

Security considerations:

  • • Never commit .env.production to git
  • • Set restrictive file permissions: chmod 600 .env.production
  • • Use environment-specific secrets (never copy from dev)
  • • Rotate credentials regularly

See also in Process

Environment Variables Reference

Complete list of production configuration options

Step 3: Caddy labels configuration

Verify docker-compose.prod.yml has correct Caddy labels:

docker-compose.prod.yml
services:
  web:
    build:
      context: .
      dockerfile: Dockerfile
      target: production
    environment:
      - NODE_ENV=production
    networks:
      - caddy
    restart: unless-stopped
    labels:
      # Domain routing
      caddy: ${PUBLIC_DOMAIN}
      caddy.reverse_proxy: "{{upstreams 4321}}"

      # HTTPS configuration
      caddy.tls.dns: cloudflare  # or your DNS provider

      # Performance
      caddy.encode: gzip
      caddy.header: "X-Frame-Options DENY"
      caddy.header.Strict-Transport-Security: "max-age=31536000"

      # Logging
      caddy.log.output: "file /var/log/caddy/access.log"
      caddy.log.format: json

networks:
  caddy:
    external: true

Note: This assumes caddy-docker-proxy is already running. If not, set it up first.

See also in Process

Caddy Setup Guide

Complete caddy-docker-proxy configuration and DNS setup

Step 4: Initial deployment

Build and start production containers:

terminal
# Build production image
docker compose -f docker-compose.yml -f docker-compose.prod.yml build --no-cache

# Start services
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Check status
docker compose ps

# Expected:
# NAME                    STATUS
# your-installation-web   Up (healthy)

Monitor initial startup:

# Follow logs during first start
docker compose logs -f web

# Watch for:
# - "astro build" completion
# - No error messages
# - Server listening on port 4321

Verify HTTPS certificate:

# Wait 30-60 seconds for certificate issuance
curl -I https://your-domain.com

# Expected:
# HTTP/2 200
# server: Caddy

# Check certificate details
openssl s_client -connect your-domain.com:443 -servername your-domain.com < /dev/null 2>/dev/null | openssl x509 -noout -dates

First deployment: Allow 2-3 minutes for DNS propagation and SSL certificate issuance.

Step 5: Configure health checks

Ensure containers have proper health checks configured:

docker-compose.prod.yml
services:
  web:
    # ... other config
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:4321/"]
      interval: 5m
      timeout: 10s
      retries: 3
      start_period: 30s

Check health status:

# View health status
docker compose ps

# Inspect detailed health
docker inspect --format='{{json .State.Health}}' your-installation-web | jq

Health check best practices:

  • • Interval: 5 minutes (balance monitoring vs overhead)
  • • Timeout: 10 seconds (generous for slow responses)
  • • Retries: 3 (prevents false positives)
  • • Start period: 30 seconds (allows initial boot)

Performance optimization

Production-specific performance tuning:

Enable compression

Already configured via Caddy label: caddy.encode: gzip

Reduces bandwidth by ~70% for text assets. Verify:

curl -I -H "Accept-Encoding: gzip" https://your-domain.com

# Look for:
# content-encoding: gzip

Browser caching

Add cache headers for static assets:

# In docker-compose.prod.yml
labels:
  caddy.handle_path: "/assets/*"
  caddy.handle_path.0_header: "Cache-Control "public, max-age=31536000, immutable""

  caddy.handle_path.1: "/*"
  caddy.handle_path.1_header: "Cache-Control "public, max-age=3600""

Resource limits

Prevent memory leaks from crashing the server:

services:
  web:
    deploy:
      resources:
        limits:
          memory: 2G
          cpus: '1.0'
        reservations:
          memory: 512M
          cpus: '0.5'

Deployment automation

Create a deployment script for consistent updates:

deploy-prod.sh
#!/bin/bash
# deploy-prod.sh - Production deployment script

set -e  # Exit on error

echo "🚀 Starting production deployment..."

# Pull latest code
echo "📥 Pulling latest code..."
git pull origin main

# Backup current state
echo "💾 Creating backup..."
docker compose ps > deployment-backup-$(date +%Y%m%d-%H%M%S).txt

# Build new image
echo "🏗️  Building production image..."
docker compose -f docker-compose.yml -f docker-compose.prod.yml build --no-cache

# Stop old containers gracefully
echo "⏸️  Stopping old containers..."
docker compose -f docker-compose.yml -f docker-compose.prod.yml down

# Start new containers
echo "▶️  Starting new containers..."
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Wait for health check
echo "🏥 Waiting for health check..."
sleep 30

# Verify deployment
echo "✅ Verifying deployment..."
if docker compose ps | grep -q "Up (healthy)"; then
    echo "✨ Deployment successful!"
    curl -I https://$(grep PUBLIC_DOMAIN .env.production | cut -d '=' -f2)
else
    echo "❌ Deployment failed - check logs"
    docker compose logs --tail=50
    exit 1
fi

# Cleanup old images
echo "🧹 Cleaning up old images..."
docker image prune -f

echo "🎉 Deployment complete!"

Make executable and run:

chmod +x deploy-prod.sh
./deploy-prod.sh

Monitoring and logging

Set up basic monitoring for production:

Container logs

# View recent logs
docker compose logs --tail=100

# Follow logs in real-time
docker compose logs -f

# Export logs to file
docker compose logs --since 24h > logs-$(date +%Y%m%d).txt

Disk usage monitoring

# Check Docker disk usage
docker system df

# Clean up unused resources
docker system prune -a --volumes

# Schedule weekly cleanup (cron)
echo "0 2 * * 0 docker system prune -af > /dev/null 2>&1" | crontab -

Uptime monitoring

Use external service (UptimeRobot, Pingdom, etc.) to monitor:

  • • HTTPS availability (every 5 minutes)
  • • Response time (< 2 seconds expected)
  • • SSL certificate expiration
  • • Status code 200 responses

Rollback procedure

If a deployment fails, roll back to previous version:

rollback.sh
# Stop current containers
docker compose down

# Revert code to previous commit
git log --oneline -5  # Find previous commit hash
git checkout <previous-commit-hash>

# Rebuild and restart
docker compose -f docker-compose.yml -f docker-compose.prod.yml build
docker compose -f docker-compose.yml -f docker-compose.prod.yml up -d

# Verify rollback
docker compose ps
curl -I https://your-domain.com

Prevention strategies:

  • • Test deployments on staging first
  • • Use git tags for production releases
  • • Keep previous Docker images (set image retention policy)
  • • Automate rollback in deployment script
  • • Monitor for 10 minutes after deployment

Production security checklist

  • HTTPS enabled and certificate auto-renews
  • Security headers set (HSTS, X-Frame-Options)
  • Firewall configured (ports 80, 443, 22 only)
  • SSH key-based authentication (password auth disabled)
  • Environment secrets not committed to git
  • Regular system updates (unattended-upgrades enabled)
  • Docker daemon secured (non-root user access)
  • Backups configured and tested

Next steps

Production deployment complete. Consider:

Loading Johnny Castaway...

Original Johnny Castaway (1992) by Sierra On-Line/Dynamix
Recreation by xesf on GitHub
Click anywhere or press ESC to exit
Click anywhere or press ESC to exit
Recreation by Bryan Braun
Original Berkeley Systems After Dark (1989)
Click anywhere or press ESC to exit
supported.systems
MISSION CONTROL
CURRENT RELEASE v6.0.0-beta.18
LAUNCH DATE Mar 4, 2026
RECENT UPDATES
  • #15668 `1118ac4` Thanks @florian-lefebvre! - Changes Ty...
  • #15726 `6f19ecc` Thanks @ocavue! - Updates dependency `...
  • #15694 `66449c9` Thanks @matthewp! - Adds `preserveBuil...
All systems nominal
Click anywhere or press ESC to exit
The web framework for content-driven websites