Files
dashboard/DOCKER_SETUP.md
Matthias Hochmeister f09748f4a1 inital
2026-02-23 17:08:58 +01:00

10 KiB

Docker Setup Documentation

Overview

This document describes the production-ready Docker setup for the Feuerwehr Dashboard application, including multi-stage builds, security best practices, and deployment instructions.

Architecture

The application consists of three Docker containers:

  1. PostgreSQL Database - postgres:16-alpine
  2. Backend API - Node.js application (TypeScript compiled to JavaScript)
  3. Frontend - React SPA served by Nginx

All containers are connected via a Docker bridge network and orchestrated using Docker Compose.

Files Structure

feuerwehr_dashboard/
├── docker-compose.yml          # Production orchestration
├── docker-compose.dev.yml      # Development orchestration
├── docker-test.sh              # Docker build testing script
├── backend/
│   ├── Dockerfile              # Multi-stage backend build
│   └── .dockerignore           # Backend Docker ignore rules
└── frontend/
    ├── Dockerfile              # Multi-stage frontend build
    ├── nginx.conf              # Nginx configuration for SPA
    └── .dockerignore           # Frontend Docker ignore rules

Backend Dockerfile

Location: /backend/Dockerfile

Features

  • Multi-stage build - Separate build and production stages
  • Build stage:
    • Based on node:20-alpine
    • Installs all dependencies (including devDependencies)
    • Compiles TypeScript to JavaScript
    • Prunes devDependencies after build
  • Production stage:
    • Based on node:20-alpine
    • Installs wget for health checks
    • Creates non-root user (nodejs:1001)
    • Copies only production node_modules
    • Copies compiled JavaScript
    • Copies database migrations to dist/
    • Runs as non-root user
    • Exposes port 3000
    • Includes health check

Build Command

cd backend
docker build -t feuerwehr-backend:latest .

Image Size

Expected final image size: ~150-200 MB (alpine-based)

Security Features

  • Non-root user execution
  • Minimal base image (Alpine Linux)
  • Only production dependencies included
  • No source code in final image

Frontend Dockerfile

Location: /frontend/Dockerfile

Features

  • Multi-stage build - Build stage with Node.js + production stage with Nginx
  • Build stage:
    • Based on node:20-alpine
    • Installs dependencies
    • Runs Vite build
    • Accepts build arguments for environment variables
  • Production stage:
    • Based on nginx:alpine
    • Installs wget for health checks
    • Copies custom nginx.conf
    • Copies built static assets
    • Configures non-root nginx user
    • Exposes port 80
    • Includes health check

Build Command

cd frontend
docker build \
  --build-arg VITE_API_URL=http://localhost:3000 \
  --build-arg VITE_APP_NAME="Feuerwehr Dashboard" \
  --build-arg VITE_APP_VERSION="1.0.0" \
  -t feuerwehr-frontend:latest .

Build Arguments

  • VITE_API_URL - Backend API URL (default: http://localhost:3000)
  • VITE_APP_NAME - Application name (default: "Feuerwehr Dashboard")
  • VITE_APP_VERSION - Application version (default: "1.0.0")

Image Size

Expected final image size: ~50-80 MB (alpine + static assets)

Security Features

  • Non-root nginx execution
  • Minimal base image (Alpine Linux)
  • Security headers configured
  • No source code in final image

Nginx Configuration

Location: /frontend/nginx.conf

Features

  1. SPA Routing

    • All routes fall back to index.html
    • Proper handling of client-side routing
  2. Performance

    • Gzip compression enabled
    • Static asset caching (1 year)
    • No caching for index.html
  3. Security Headers

    • X-Frame-Options: SAMEORIGIN
    • X-Content-Type-Options: nosniff
    • X-XSS-Protection: 1; mode=block
    • Referrer-Policy: strict-origin-when-cross-origin
  4. Health Check

    • Endpoint: /health
    • Returns 200 with "healthy" text
  5. Error Handling

    • 404 errors redirect to index.html
    • Custom 50x error page

Docker Compose

Location: /docker-compose.yml

Services

PostgreSQL

postgres:
  image: postgres:16-alpine
  ports: 5432:5432
  volumes: postgres_data_prod
  health_check: pg_isready
  restart: unless-stopped

Backend

backend:
  build: ./backend
  ports: 3000:3000
  depends_on: postgres (healthy)
  health_check: wget localhost:3000/health
  restart: unless-stopped

Frontend

frontend:
  build: ./frontend
  build_args: VITE_API_URL
  ports: 80:80
  depends_on: backend (healthy)
  health_check: wget localhost:80/health
  restart: unless-stopped

Environment Variables

Create a .env file based on .env.example:

Required:

  • POSTGRES_PASSWORD - Database password
  • JWT_SECRET - JWT signing secret

Optional:

  • POSTGRES_DB - Database name (default: feuerwehr_prod)
  • POSTGRES_USER - Database user (default: prod_user)
  • POSTGRES_PORT - Database port (default: 5432)
  • BACKEND_PORT - Backend port (default: 3000)
  • FRONTEND_PORT - Frontend port (default: 80)
  • CORS_ORIGIN - CORS origin (default: http://localhost:80)
  • VITE_API_URL - Frontend API URL (default: http://localhost:3000)

Networks

  • feuerwehr_network - Bridge network connecting all services

Volumes

  • postgres_data_prod - Persistent PostgreSQL data

Usage

1. Test Docker Builds

Run the test script to verify Docker builds work:

./docker-test.sh

This script will:

  • Check Docker availability
  • Build backend image
  • Build frontend image
  • Report success/failure
  • Optionally cleanup test images

2. Configure Environment

Copy and configure environment file:

cp .env.example .env
# Edit .env and set required variables

3. Build and Start Services

# Build and start all services
docker-compose up -d

# View logs
docker-compose logs -f

# Check service status
docker-compose ps

4. Access Application

5. Stop Services

# Stop all services
docker-compose down

# Stop and remove volumes
docker-compose down -v

Health Checks

All services include health checks:

PostgreSQL

  • Command: pg_isready -U $USER -d $DB
  • Interval: 10s
  • Retries: 5

Backend

  • Command: wget localhost:3000/health
  • Interval: 30s
  • Retries: 3
  • Start Period: 40s

Frontend

  • Command: wget localhost:80/health
  • Interval: 30s
  • Retries: 3
  • Start Period: 30s

Troubleshooting

Backend Build Fails

  1. Check TypeScript compilation:
cd backend
npm install
npm run build
  1. Verify all source files exist
  2. Check tsconfig.json configuration

Frontend Build Fails

  1. Check Vite build:
cd frontend
npm install
npm run build
  1. Verify all dependencies are installed
  2. Check build arguments are correct

Container Won't Start

  1. Check logs:
docker-compose logs [service-name]
  1. Verify environment variables in .env
  2. Check health check status:
docker-compose ps

Database Connection Issues

  1. Verify DATABASE_URL format:
postgresql://user:password@postgres:5432/database
  1. Check postgres service is healthy:
docker-compose ps postgres
  1. Verify network connectivity:
docker-compose exec backend ping postgres

Security Considerations

Production Checklist

  • Set strong POSTGRES_PASSWORD
  • Set strong JWT_SECRET (min 32 characters)
  • Use HTTPS in production
  • Configure proper CORS_ORIGIN
  • Enable firewall rules
  • Regular security updates
  • Monitor container logs
  • Backup database regularly

Image Security

  • Alpine Linux base (minimal attack surface)
  • Non-root user execution
  • No unnecessary packages
  • Security headers enabled
  • Health checks configured

Maintenance

Update Dependencies

# Rebuild images with latest dependencies
docker-compose build --no-cache
docker-compose up -d

Backup Database

# Create backup
docker-compose exec postgres pg_dump -U $USER $DB > backup.sql

# Restore backup
docker-compose exec -T postgres psql -U $USER $DB < backup.sql

View Logs

# All services
docker-compose logs -f

# Specific service
docker-compose logs -f backend

# Last 100 lines
docker-compose logs --tail=100 backend

Restart Services

# Restart all
docker-compose restart

# Restart specific service
docker-compose restart backend

Performance Optimization

Build Cache

Docker uses layer caching. To optimize:

  1. Copy package files first
  2. Install dependencies
  3. Copy source code last

This ensures dependency installation is cached.

Image Size

Current image sizes:

  • Backend: ~150-200 MB
  • Frontend: ~50-80 MB
  • Total: ~200-280 MB

Resource Limits

Add resource limits in docker-compose.yml:

services:
  backend:
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M

Monitoring

Container Stats

docker stats

Health Status

docker-compose ps

Resource Usage

docker system df

Production Deployment

Using Docker Compose

# On production server
git clone <repository>
cd feuerwehr_dashboard
cp .env.example .env
# Configure .env with production values
docker-compose up -d

Using Container Registry

# Build and tag images
docker build -t registry.example.com/feuerwehr-backend:v1.0.0 backend/
docker build -t registry.example.com/feuerwehr-frontend:v1.0.0 frontend/

# Push to registry
docker push registry.example.com/feuerwehr-backend:v1.0.0
docker push registry.example.com/feuerwehr-frontend:v1.0.0

# Pull and run on production
docker pull registry.example.com/feuerwehr-backend:v1.0.0
docker pull registry.example.com/feuerwehr-frontend:v1.0.0
docker-compose up -d

Additional Resources