Skip to main content
This guide provides deep technical details about LiveCodes Docker deployment. For a quick start, see the Self-Hosting Guide.

Dockerfile Architecture

LiveCodes uses a multi-stage Docker build for optimal image size and security:

Stage 1: Builder

FROM node:24.4.1-alpine3.22 AS builder

RUN apk update --no-cache && apk add --no-cache git

WORKDIR /app

# Copy package files for all workspaces
COPY package*.json ./
COPY docs/package*.json docs/
COPY storybook/package*.json storybook/
COPY server/package*.json server/

RUN npm ci

COPY . .
Key features:
  • Uses Alpine Linux for minimal image size
  • Installs dependencies before copying source for better layer caching
  • Supports monorepo structure with multiple package.json files

Build Arguments

The builder stage accepts these build arguments:
ARG SELF_HOSTED
ARG SELF_HOSTED_SHARE
ARG SELF_HOSTED_BROADCAST
ARG BROADCAST_PORT
ARG SANDBOX_HOST_NAME
ARG SANDBOX_PORT
ARG FIREBASE_CONFIG
ARG DOCS_BASE_URL
ARG NODE_OPTIONS
These are compile-time constants embedded into the application bundle.

Conditional Build

RUN if [ "$DOCS_BASE_URL" == "null" ]; \
  then npm run build:app; \
  else npm run build; \
  fi
  • build:app: Builds only the application (no documentation)
  • build: Builds application + documentation site

Stage 2: Production Server

FROM node:24.4.1-alpine3.22 AS server

# Create non-root user
RUN addgroup -S appgroup
RUN adduser -S appuser -G appgroup

RUN mkdir -p /srv && chown -R appuser:appgroup /srv

USER appuser

WORKDIR /srv

COPY server/package*.json ./
RUN npm ci

# Copy build artifacts from builder stage
COPY --from=builder /app/build/ build/
COPY functions/ functions/
COPY server/src/ server/src/
COPY src/livecodes/html/sandbox/ server/src/sandbox/

CMD ["node", "server/src/app.ts"]
Security features:
  • Runs as non-root user (appuser)
  • Minimal production dependencies only
  • Single responsibility: serve the application

Docker Compose Stack

The complete stack consists of three services:

App Service

app:
  build:
    context: .
    dockerfile: Dockerfile
    args:
      - SELF_HOSTED=true
      - SELF_HOSTED_SHARE=${SELF_HOSTED_SHARE:-true}
      - SELF_HOSTED_BROADCAST=${SELF_HOSTED_BROADCAST:-true}
      - BROADCAST_PORT=${BROADCAST_PORT:-3030}
      - SANDBOX_HOST_NAME=${SANDBOX_HOST_NAME:-${HOST_NAME:-livecodes.localhost}}
      - SANDBOX_PORT=${SANDBOX_PORT:-8090}
      - FIREBASE_CONFIG=${FIREBASE_CONFIG:-}
      - DOCS_BASE_URL=${DOCS_BASE_URL:-null}
      - LOCAL_MODULES=${LOCAL_MODULES:-false}
      - NODE_OPTIONS=--max-old-space-size=4096
  restart: unless-stopped
  environment:
    - SELF_HOSTED=true
    - HOST_NAME=${HOST_NAME:-livecodes.localhost}
    - PORT=${PORT:-443}
    - BROADCAST_TOKENS=${BROADCAST_TOKENS:-}
    - SANDBOX_HOST_NAME=${SANDBOX_HOST_NAME:-${HOST_NAME:-livecodes.localhost}}
    - SANDBOX_PORT=${SANDBOX_PORT:-8090}
    - LOG_URL=${LOG_URL:-null}
    - VALKEY_HOST=valkey
    - VALKEY_PORT=6379
    - NODE_OPTIONS=--max-old-space-size=4096
  volumes:
    - ./assets:/srv/build/assets
  depends_on:
    - valkey
Key features:
  • Automatic restart on failure
  • Custom assets via volume mount
  • Connects to Valkey for data persistence
  • 4GB memory allocation for Node.js

Valkey Service (Redis Alternative)

valkey:
  image: valkey/valkey:8.1.2-alpine3.22
  restart: on-failure
  volumes:
    - valkey-data:/data
  command:
    [
      "sh",
      "-c",
      'if [ "$SELF_HOSTED_SHARE" != "false" ]; then valkey-server --save 60 1 --loglevel warning; fi',
    ]
Features:
  • Persists data to named volume valkey-data
  • Auto-saves every 60 seconds if ≥1 change
  • Only runs if sharing is enabled
  • Minimal logging (warnings only)
Valkey is a Redis-compatible database. You can substitute with Redis if preferred.

Server Service (Caddy)

server:
  image: caddy:2.10.0-alpine
  entrypoint: ["/bin/sh", "./entrypoint.sh"]
  command:
    [
      "caddy",
      "run",
      "--config",
      "/etc/caddy/Caddyfile",
      "--adapter",
      "caddyfile",
    ]
  restart: unless-stopped
  ports:
    - "80:80"
    - "${PORT:-443}:${PORT:-443}"
    - "${SANDBOX_PORT:-8090}:${SANDBOX_PORT:-8090}"
    - "${BROADCAST_PORT:-3030}:${BROADCAST_PORT:-3030}"
  environment:
    - HOST_NAME=${HOST_NAME:-livecodes.localhost}
    - PORT=${PORT:-443}
    - SANDBOX_HOST_NAME=${SANDBOX_HOST_NAME:-${HOST_NAME:-livecodes.localhost}}
    - SANDBOX_PORT=${SANDBOX_PORT:-8090}
    - BROADCAST_PORT=${BROADCAST_PORT:-3030}
  volumes:
    - ./server/caddy/entrypoint.sh:/srv/entrypoint.sh
    - ./server/caddy/Caddyfile:/etc/caddy/Caddyfile
    - ./server/data/caddy/data:/data
    - ./server/data/caddy/config:/config
  depends_on:
    - app
Features:
  • Automatic HTTPS with Let’s Encrypt
  • Reverse proxy for all services
  • HTTP to HTTPS redirect
  • Persistent TLS certificates

Volume Configuration

Named Volumes

volumes:
  valkey-data:
    name: livecodes-share-data
The livecodes-share-data volume persists shared project data across container restarts and updates.

Bind Mounts

Custom assets:
volumes:
  - ./assets:/srv/build/assets
Place custom files in ./assets/ to override defaults. Caddy configuration:
volumes:
  - ./server/caddy/Caddyfile:/etc/caddy/Caddyfile
  - ./server/data/caddy/data:/data
  - ./server/data/caddy/config:/config
Persists TLS certificates and Caddy configuration.

Network Architecture

Default Bridge Network

All services communicate via Docker’s default bridge network:
┌─────────────────────────────────────────┐
│  Docker Bridge Network                  │
│                                         │
│  ┌─────────┐    ┌─────────┐           │
│  │  Caddy  │───▶│   App   │           │
│  │ (proxy) │    │ (node)  │           │
│  └─────────┘    └─────────┘           │
│       │              │                 │
│       │         ┌────▼─────┐          │
│       │         │  Valkey  │          │
│       │         │ (redis)  │          │
│       │         └──────────┘          │
│       │                               │
└───────┼───────────────────────────────┘


   External Access
   (ports 80, 443, 8090, 3030)

Service Resolution

Services resolve each other by name:
  • App → Valkey: valkey:6379
  • Caddy → App: app:443

Custom Build Examples

Build Without Documentation

docker build \
  --build-arg SELF_HOSTED=true \
  --build-arg DOCS_BASE_URL=null \
  --build-arg SANDBOX_HOST_NAME=sandbox.example.com \
  -t livecodes:latest .

Build with Custom Firebase

docker build \
  --build-arg FIREBASE_CONFIG='{"apiKey":"...","authDomain":"..."}' \
  -t livecodes:custom .

Production Build with Optimizations

docker build \
  --build-arg NODE_OPTIONS="--max-old-space-size=8192" \
  --build-arg SELF_HOSTED=true \
  --no-cache \
  -t livecodes:production .

Docker Compose Profiles

Development Profile

# docker-compose.dev.yml
services:
  app:
    build:
      args:
        - DOCS_BASE_URL=http://localhost:3000
    environment:
      - NODE_ENV=development
    ports:
      - "3000:3000"
Run with: docker-compose -f docker-compose.yml -f docker-compose.dev.yml up

Production Profile

# docker-compose.prod.yml
services:
  app:
    environment:
      - NODE_ENV=production
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 4G
        reservations:
          cpus: '1'
          memory: 2G

Health Checks

Add health checks to ensure service availability:
app:
  healthcheck:
    test: ["CMD", "node", "-e", "require('http').get('http://localhost:443/', (r) => process.exit(r.statusCode === 200 ? 0 : 1))"]
    interval: 30s
    timeout: 10s
    retries: 3
    start_period: 40s

valkey:
  healthcheck:
    test: ["CMD", "valkey-cli", "ping"]
    interval: 5s
    timeout: 3s
    retries: 5

Scaling Considerations

Horizontal Scaling

app:
  deploy:
    replicas: 3
When scaling horizontally, use external Redis/Valkey with shared access, not the container instance.

Load Balancing

server:
  environment:
    - CADDY_UPSTREAM="app:443 app-2:443 app-3:443"

Security Hardening

Read-Only Root Filesystem

app:
  read_only: true
  tmpfs:
    - /tmp
    - /srv/.npm

Drop Capabilities

app:
  cap_drop:
    - ALL
  cap_add:
    - NET_BIND_SERVICE

Security Options

app:
  security_opt:
    - no-new-privileges:true
  pids_limit: 100

Backup and Recovery

Backup Valkey Data

# Create backup
docker run --rm \
  -v livecodes-share-data:/data \
  -v $(pwd)/backups:/backup \
  alpine tar czf /backup/valkey-backup-$(date +%Y%m%d).tar.gz /data

Restore from Backup

# Restore backup
docker run --rm \
  -v livecodes-share-data:/data \
  -v $(pwd)/backups:/backup \
  alpine tar xzf /backup/valkey-backup-20260303.tar.gz -C /

Monitoring

Container Stats

docker stats --format "table {{.Name}}\t{{.CPUPerc}}\t{{.MemUsage}}"

Logs Aggregation

app:
  logging:
    driver: json-file
    options:
      max-size: "10m"
      max-file: "3"
      labels: "service,environment"

External Monitoring

Integrate with Prometheus:
app:
  labels:
    - "prometheus.io/scrape=true"
    - "prometheus.io/port=3000"

Troubleshooting

Increase Docker memory limit:
docker build --memory=8g -t livecodes .
Check volume permissions:
sudo chown -R 1000:1000 ./assets
sudo chown -R 1000:1000 ./server/data
Verify network:
docker network inspect livecodes_default
docker-compose exec app ping valkey

Next Steps

Services Architecture

Understand the internal services

Security Model

Security best practices

Performance

Optimize Docker performance

Self-Hosting

Back to self-hosting guide

Build docs developers (and LLMs) love