This guide covers Docker-specific deployment scenarios, image building, and production best practices.
Official Docker image
PlanningSup publishes official images to GitHub Container Registry:
gh cr.io/kernoeb/planningsup:latest
latest - Latest stable release
vX.Y.Z - Specific version tags (e.g., v3.0.0)
main - Latest commit on main branch (may be unstable)
Image architecture
The production image uses a multi-stage build for minimal size and security.
Build stage
FROM oven/bun:1 AS build
# Install Node.js 24 for tooling
RUN apt-get update && apt-get install -y curl && \
curl -fsSL https://deb.nodesource.com/setup_24.x | bash - && \
apt-get install -y nodejs
WORKDIR /app
# Install dependencies
COPY package.json bun.lock ./
RUN bun install --frozen-lockfile
# Copy source and build
COPY . .
RUN bun run lint && bun run typecheck
RUN bun run build
# Run unit tests
ENV NODE_ENV=test
RUN bun run test:unit
The Docker build runs linting, type checking, building, and unit tests to ensure image quality.
Runtime stage
FROM gcr.io/distroless/base-debian12
# Copy only runtime artifacts
COPY --from=build /app/apps/api/server ./server
COPY --from=build /app/apps/web/dist ./web/dist/
COPY --from=build /app/plannings ./plannings
ENV NODE_ENV=production
ENV PORT=20000
CMD [ "./server" ]
EXPOSE $PORT
The final image uses Google’s distroless base for:
Minimal attack surface (no package manager, shell by default)
Small image size
Security hardening
Building custom images
Build locally
# Build with default settings
bun run docker:build
# This creates an image tagged as 'test-planningsup'
The build script is defined in package.json:
{
"scripts" : {
"docker:build" : "docker build -t test-planningsup ."
}
}
Build with custom tag
docker build -t myregistry/planningsup:v1.0.0 .
Build arguments
The Dockerfile accepts a BUN_VERSION argument:
docker build --build-arg BUN_VERSION= 1.1.0 -t planningsup:bun-1.1.0 .
Deployment configurations
Development stack
The docker-compose.yml provides a minimal development database:
services :
postgres :
image : postgres:18
container_name : planningsup_db
environment :
POSTGRES_USER : planningsup
POSTGRES_PASSWORD : mysecretpassword
POSTGRES_DB : planningsup
ports :
- '5432:5432'
volumes :
- pgdata:/var/lib/postgresql
networks :
- planningsup_network
healthcheck :
test : [ 'CMD-SHELL' , 'pg_isready' ]
interval : 10s
timeout : 5s
retries : 5
volumes :
pgdata :
name : planningsup_pgdata
networks :
planningsup_network :
driver : bridge
name : planningsup_network
Production stack
The docker-compose.prod.yml adds the webapp service:
services :
postgres :
image : postgres:18
container_name : planningsup_db
env_file : db.env
restart : always
volumes :
- ./postgres_data:/var/lib/postgresql
networks :
- planningsup_network
healthcheck :
test : [ 'CMD-SHELL' , 'pg_isready' ]
interval : 10s
timeout : 5s
retries : 5
webapp :
image : ghcr.io/kernoeb/planningsup
container_name : planningsup_webapp
env_file : webapp.env
restart : always
depends_on :
postgres :
condition : service_healthy
restart : true
ports :
- '31021:20000'
networks :
- planningsup_network
networks :
planningsup_network :
driver : bridge
Test stacks
PlanningSup includes dedicated test configurations:
Standard integration tests
docker-compose.test.yml runs with AUTH_ENABLED=false:
services :
postgres-test :
image : postgres:18
container_name : planningsup_db_test
ports :
- '5433:5432'
# ...
app-test :
build : .
container_name : planningsup_test_app
environment :
DATABASE_URL : postgresql://testuser:testpass@postgres-test:5432/planningsup_test
AUTH_ENABLED : false
PORT : 20000
ports :
- '20000:20000'
Authentication tests
docker-compose.test-auth.yml runs with AUTH_ENABLED=true:
services :
postgres-test-auth :
# ...
ports :
- '5434:5432'
app-test-auth :
# ...
environment :
AUTH_ENABLED : true
PORT : 20001
ports :
- '20001:20001'
Production deployment patterns
Single server deployment
Set up environment files
Create db.env and webapp.env with production values.
Deploy with Docker Compose
docker compose -f docker-compose.prod.yml up -d
Configure reverse proxy
Point Nginx, Caddy, or Traefik to port 31021.
High availability setup
Load balanced
Database replication
Deploy multiple webapp containers behind a load balancer: services :
webapp-1 :
image : ghcr.io/kernoeb/planningsup
# ...
webapp-2 :
image : ghcr.io/kernoeb/planningsup
# ...
nginx :
image : nginx
volumes :
- ./nginx.conf:/etc/nginx/nginx.conf
ports :
- '80:80'
Use PostgreSQL streaming replication: services :
postgres-primary :
image : postgres:18
environment :
POSTGRES_REPLICATION : true
# ...
postgres-replica :
image : postgres:18
environment :
POSTGRES_PRIMARY_HOST : postgres-primary
# ...
Kubernetes deployment
Example Kubernetes manifests:
deployment.yaml
service.yaml
ingress.yaml
apiVersion : apps/v1
kind : Deployment
metadata :
name : planningsup
spec :
replicas : 3
selector :
matchLabels :
app : planningsup
template :
metadata :
labels :
app : planningsup
spec :
containers :
- name : planningsup
image : ghcr.io/kernoeb/planningsup:latest
ports :
- containerPort : 20000
env :
- name : DATABASE_URL
valueFrom :
secretKeyRef :
name : planningsup-secrets
key : database-url
- name : PORT
value : "20000"
- name : RUN_JOBS
value : "true"
livenessProbe :
httpGet :
path : /api/ping
port : 20000
initialDelaySeconds : 30
periodSeconds : 10
readinessProbe :
httpGet :
path : /api/ping
port : 20000
initialDelaySeconds : 10
periodSeconds : 5
Managing containers
View logs
# All services
docker compose logs
# Specific service
docker compose logs webapp
# Follow logs
docker compose logs -f
# Last 100 lines
docker compose logs --tail=100 webapp
Restart services
# Restart all
docker compose restart
# Restart webapp only
docker compose restart webapp
# Recreate containers
docker compose up -d --force-recreate
Update deployment
# Pull latest images
docker compose pull
# Recreate with new images
docker compose up -d
Execute commands in container
# Open shell (if available)
docker exec -it planningsup_webapp sh
# Run specific command
docker exec planningsup_webapp ls /app/plannings
Health monitoring
Docker health checks
View container health status:
Output shows health status:
NAME STATUS PORTS
planningsup_webapp Up 2 hours (healthy) 0.0.0.0:31021->20000/tcp
planningsup_db Up 2 hours (healthy) 5432/tcp
Manual health checks
# API health
curl http://localhost:31021/api/ping
# Database health
docker exec planningsup_db pg_isready
# Operations status (requires OPS_TOKEN)
curl -H "x-ops-token: YOUR_TOKEN" http://localhost:31021/api/ops/plannings
Troubleshooting
Build failures
Show Lint or typecheck errors
The Docker build runs bun run lint and bun run typecheck. If these fail, fix the issues locally first: bun run lint-fix
bun run typecheck
The build also runs bun run test:unit. Ensure tests pass locally:
Increase Docker memory limits in Docker Desktop settings or use build arguments: docker build --memory=4g -t planningsup .
Runtime issues
Show Container exits immediately
Check logs for startup errors: docker logs planningsup_webapp
Common issues:
Invalid DATABASE_URL
Missing required environment variables
Database not ready (check depends_on and healthcheck)
Show Database connection refused
Ensure containers are on the same network: docker network inspect planningsup_network
Verify the database hostname matches the service name in docker-compose.yml.
Show Planning data missing
Check that planning JSON files are mounted or copied correctly: docker exec planningsup_webapp ls /app/plannings
CI/CD integration
PlanningSup uses GitHub Actions for automated builds and tests.
Workflow overview
The .github/workflows/docker-publish.yml workflow:
Builds the Docker image
Runs integration tests (auth disabled and enabled)
Runs E2E tests (only when UI files change)
Publishes to GitHub Container Registry (on main branch or version tags)
Publishing images
Images are published only:
On whitelisted branches (main, dev)
On valid vX.Y.Z tags
NOT from fork pull requests
Running tests in CI
# Integration tests (no auth)
bun run test:integration
# Integration tests (with auth)
bun run test:integration:auth
# E2E tests
bun run test:e2e
See the Testing Guide for more details.
Next steps
Environment variables Complete reference of all configuration options
Architecture Understand the system design and components