Docker Production Deployment

This guide covers deploying AutoCom using Docker Compose with production-optimized configurations.

Prerequisites

  • Docker 24.0+
  • Docker Compose 2.0+
  • At least 4GB RAM
  • 20GB available disk space

Production Dockerfiles

AutoCom uses multi-stage Dockerfiles optimized for production for both the backend and frontend.

Backend Dockerfile (backend/Dockerfile.prod)

# Stage 1: Dependencies - Install Composer packages
FROM composer:latest AS composer-deps
WORKDIR /app
COPY composer.json composer.lock ./
RUN composer install \
    --no-dev \
    --no-scripts \
    --no-autoloader \
    --no-interaction \
    --prefer-dist \
    --ignore-platform-reqs

# Stage 2: Base - Build PHP extensions
FROM php:8.4-fpm-alpine AS base
# Install extensions: pdo_pgsql, redis, opcache, etc.
# Copy optimized PHP configuration
# Generate optimized autoloader

# Stage 3: Production - Minimal runtime
FROM php:8.4-fpm-alpine AS production
# Copy only what's needed for runtime
# Set proper permissions

Frontend Dockerfile (frontend/Dockerfile.prod)

The frontend uses Next.js standalone output mode, producing a self-contained Node.js server with only the required dependencies:

# syntax=docker/dockerfile:1.6
# Stage 1: Dependencies — cached unless package.json/bun.lock change
FROM oven/bun:1-alpine AS deps
COPY frontend/package.json frontend/bun.lock ./
RUN --mount=type=cache,target=/root/.bun/install/cache \
    bun install --frozen-lockfile

# Stage 2: Build — config files before source for layer caching
FROM oven/bun:1-alpine AS builder
COPY --from=deps /app/node_modules ./node_modules
COPY frontend/next.config.js frontend/tsconfig.json ...  # Config files first
COPY modules/ ../modules/                                  # Module frontend code
COPY frontend/app frontend/components frontend/lib ...     # Source code last
RUN --mount=type=cache,target=/app/.next/cache \
    bun run build

# Stage 3: Runner — minimal production image
# Standalone server only needs Node, so we drop bun for the runtime image.
FROM node:20-alpine AS runner
COPY --from=builder /app/.next/standalone ./
COPY --from=builder /app/.next/static ./.next/static
COPY --from=builder /app/public ./public
CMD ["node", "server.js"]

The final frontend image is approximately 280MB and runs via node server.js (no bun/npm at runtime, only the bundled node_modules required by Next.js standalone).

Key Optimizations

Optimization Benefit
Multi-stage build (backend) Smaller final image (~150MB)
Multi-stage build (frontend) Standalone output (~280MB image)
Alpine base Minimal attack surface
OPcache precompilation Faster PHP startup time
Route/View caching Reduced runtime overhead
Layer caching (frontend) Config files copied before source — bun install cached on code-only changes
BuildKit cache mounts /root/.bun/install/cache + .next/cache survive between rebuilds
Standalone output node server.js — no bun/npm runtime, only required node_modules bundled

Incremental Rebuild Times

Change Type Rebuild Time Notes
Code-only (frontend) ~30–40s install layer cached, only bun run build re-runs
New dependency ~50–60s install re-runs but the bun cache mount keeps deps warm
Backend PHP change ~30s Composer deps cached, only app code changes
SDUI module (backend only) ~30s Frontend image not rebuilt — see SDUI Deployment

Building Production Images

# Build all production images
docker compose -f docker-compose.prod.yml build

# Build specific service
docker compose -f docker-compose.prod.yml build app

# Build without cache (for clean rebuild)
docker compose -f docker-compose.prod.yml build --no-cache

Docker Compose Configuration

Environment Variables

Create a .env file in the project root:

# Application
APP_NAME=AutoCom
APP_ENV=production
APP_DEBUG=false
APP_URL=https://your-domain.com

# Database
DB_CONNECTION=pgsql
DB_HOST=postgres
DB_PORT=5432
DB_DATABASE=autocom
DB_USERNAME=autocom
DB_PASSWORD=your-secure-password

# Redis
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_PASSWORD=your-redis-password

# Cache & Queue
CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

Production Compose File

The docker-compose.prod.yml includes:

services:
  app:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod
    environment:
      - APP_ENV=production
    depends_on:
      - postgres
      - redis

  nginx:
    image: nginx:alpine
    volumes:
      - ./docker/nginx/nginx.conf:/etc/nginx/nginx.conf:ro
    ports:
      - "80:80"
      - "443:443"

  postgres:
    image: postgres:16-alpine
    volumes:
      - postgres_data:/var/lib/postgresql/data
    environment:
      POSTGRES_DB: autocom
      POSTGRES_USER: autocom
      POSTGRES_PASSWORD: ${DB_PASSWORD}

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD}
    volumes:
      - redis_data:/data

  horizon:
    build:
      context: ./backend
      dockerfile: Dockerfile.prod
    command: php artisan horizon
    depends_on:
      - app
      - redis

  frontend:
    build:
      context: ./frontend
      dockerfile: Dockerfile.prod
    environment:
      - NEXT_PUBLIC_API_URL=${APP_URL}/api/v1

volumes:
  postgres_data:
  redis_data:

Starting Production Services

# Start all services in detached mode
docker compose -f docker-compose.prod.yml up -d

# View logs
docker compose -f docker-compose.prod.yml logs -f

# Check service status
docker compose -f docker-compose.prod.yml ps

Running Migrations

# Run database migrations
docker compose -f docker-compose.prod.yml exec app php artisan migrate --force

# Seed initial data (if needed)
docker compose -f docker-compose.prod.yml exec app php artisan db:seed --force

Health Checks

The production setup includes health checks for all services:

PHP-FPM Health Check

# Check PHP-FPM ping endpoint
docker compose exec app /usr/local/bin/health-check.sh

The health check script uses FastCGI to ping PHP-FPM:

#!/bin/sh
SCRIPT_NAME=/fpm-ping \
SCRIPT_FILENAME=/fpm-ping \
REQUEST_METHOD=GET \
cgi-fcgi -bind -connect 127.0.0.1:9000 | grep -q pong

Nginx Health Check

# Check nginx health endpoint
curl http://localhost/health

SSL/TLS Configuration

For production, configure SSL using Let's Encrypt:

# Add to docker-compose.prod.yml
services:
  nginx:
    volumes:
      - ./certbot/conf:/etc/letsencrypt:ro
      - ./certbot/www:/var/www/certbot:ro

  certbot:
    image: certbot/certbot
    volumes:
      - ./certbot/conf:/etc/letsencrypt
      - ./certbot/www:/var/www/certbot
    command: certonly --webroot -w /var/www/certbot -d your-domain.com

Backup Configuration

Database Backup

# Create backup
docker compose exec postgres pg_dump -U autocom autocom > backup.sql

# Restore backup
docker compose exec -T postgres psql -U autocom autocom < backup.sql

Redis Backup

# Trigger Redis save
docker compose exec redis redis-cli -a ${REDIS_PASSWORD} BGSAVE

# Copy RDB file
docker cp $(docker compose ps -q redis):/data/dump.rdb ./redis-backup.rdb

Monitoring

View Resource Usage

# Check container stats
docker stats

# Check specific service
docker compose -f docker-compose.prod.yml top app

Log Management

# View all logs
docker compose -f docker-compose.prod.yml logs -f

# View specific service logs
docker compose -f docker-compose.prod.yml logs -f app

# Tail last 100 lines
docker compose -f docker-compose.prod.yml logs --tail=100 app

Troubleshooting

Common Issues

Container won't start:

# Check logs for errors
docker compose -f docker-compose.prod.yml logs app

# Verify environment variables
docker compose -f docker-compose.prod.yml config

Database connection issues:

# Verify postgres is running
docker compose exec postgres pg_isready

# Check connection from app
docker compose exec app php artisan db:monitor

Permission errors:

# Fix storage permissions
docker compose exec app chmod -R 755 storage bootstrap/cache
docker compose exec app chown -R www-data:www-data storage bootstrap/cache

SDUI Module Deployment

Modules that use the SDUI system define their screens as backend JSON. Because the web and mobile renderers fetch screen definitions from the API at runtime, SDUI modules do not require a frontend rebuild. Only the backend image needs updating.

When You Need to Rebuild

Change Backend rebuild Frontend rebuild
New SDUI module (backend screen providers) Yes No
Update SDUI screen definition Yes No
New frontend page (non-SDUI) No Yes
New shadcn component or UI change No Yes
Backend API change Yes No

Module Deployment Workflow

Adding a new SDUI module:

# 1. Rebuild backend only (includes new screen provider PHP classes)
docker compose -f docker-compose.prod.yml build app

# 2. Restart the backend
docker compose -f docker-compose.prod.yml up -d app

# 3. Sync module metadata to the database
docker compose -f docker-compose.prod.yml exec app php artisan module:sync

The new module's screens are immediately accessible at /m/{module}/{page} on the web and in the mobile app sidebar — no frontend image rebuild needed.

Updating an existing module's screens:

# Same as above — rebuild backend, restart, sync
docker compose -f docker-compose.prod.yml build app
docker compose -f docker-compose.prod.yml up -d app
docker compose -f docker-compose.prod.yml exec app php artisan module:sync

Full stack rebuild (frontend + backend changes):

# Build both images
docker compose -f docker-compose.prod.yml build app frontend

# Restart both services
docker compose -f docker-compose.prod.yml up -d app frontend

# Sync modules
docker compose -f docker-compose.prod.yml exec app php artisan module:sync

Important: Backend code is baked into the Docker image (not volume-mounted in production). You must rebuild the app image after any PHP changes — including new screen providers, route changes, or migration files.

Next Steps