Skip to content

Docker Deployment

This guide covers deploying the School Clubs Management System using Docker and Docker Compose.

Overview

The application is containerized using Docker for consistent deployment across environments. The setup includes:

  • Flask application container
  • SQLite database with persistent volume
  • Traefik reverse proxy for HTTPS
  • Automated deployment via GitHub Actions

Prerequisites

Required Software

Optional

  • Domain name with DNS configured
  • Traefik reverse proxy (for HTTPS)

Quick Start

1. Clone Repository

git clone https://github.com/DrKarlheinz/SE1.git
cd SE1

2. Configure Environment

Create .env file:

cat > .env << EOF
SECRET_KEY=$(python3 -c "import secrets; print(secrets.token_hex(32))")
DATABASE_URI=sqlite:////data/data.db
FLASK_ENV=production
EOF

3. Build and Start

docker compose up -d --build

4. Access Application

Application Running

  • Local: http://localhost:5000
  • Production: https://se.unilu.dev
  • Docs: https://docs.unilu.dev

Docker Configuration

Dockerfile

FROM python:3.12-slim

WORKDIR /app

# Install dependencies
COPY deliverable-2/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy application
COPY deliverable-2/school_clubs /app/school_clubs

# Create instance directory
RUN mkdir -p /data

# Set environment variables
ENV FLASK_APP=school_clubs
ENV PYTHONUNBUFFERED=1

# Expose port
EXPOSE 5000

# Run application
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "school_clubs:create_app()"]

Key Points:

  • Uses Python 3.12 slim image
  • Installs dependencies from requirements.txt
  • Creates /data directory for SQLite database
  • Uses Gunicorn as WSGI server
  • Exposes port 5000

docker-compose.yml

version: '3.8'

services:
  app:
    build: .
    container_name: school-clubs-app
    restart: unless-stopped
    ports:
      - "5000:5000"
    volumes:
      - school-clubs-data:/data
    environment:
      - SECRET_KEY=${SECRET_KEY:-only-set-server-side}
      - DATABASE_URI=sqlite:////data/data.db
      - FLASK_ENV=production
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.school-clubs.rule=Host(`se.unilu.dev`)"
      - "traefik.http.routers.school-clubs.entrypoints=websecure"
      - "traefik.http.routers.school-clubs.tls.certresolver=letsencrypt"
      - "traefik.http.services.school-clubs.loadbalancer.server.port=5000"
    networks:
      - traefik-network

  docs:
    image: squidfunk/mkdocs-material:latest
    container_name: school-clubs-docs
    restart: unless-stopped
    volumes:
      - ./docs:/docs
    command: serve --dev-addr=0.0.0.0:8000
    ports:
      - "8000:8000"
    labels:
      - "traefik.enable=true"
      - "traefik.http.routers.school-clubs-docs.rule=Host(`docs.unilu.dev`)"
      - "traefik.http.routers.school-clubs-docs.entrypoints=websecure"
      - "traefik.http.routers.school-clubs-docs.tls.certresolver=letsencrypt"
      - "traefik.http.services.school-clubs-docs.loadbalancer.server.port=8000"
    networks:
      - traefik-network

volumes:
  school-clubs-data:
    driver: local

networks:
  traefik-network:
    external: true

Environment Variables

Application Variables

Variable Required Default Description
SECRET_KEY Yes - Flask secret key for sessions
DATABASE_URI No sqlite:////data/data.db Database connection string
FLASK_ENV No production Environment: development/production

Setting Variables

Option 1: .env File

# .env
SECRET_KEY=your-secret-key-here
DATABASE_URI=sqlite:////data/data.db
FLASK_ENV=production

Option 2: Environment Variables

export SECRET_KEY="your-secret-key-here"
export DATABASE_URI="sqlite:////data/data.db"
docker compose up -d

Option 3: docker-compose.yml

services:
  app:
    environment:
      - SECRET_KEY=your-secret-key-here
      - DATABASE_URI=sqlite:////data/data.db

Security Warning

Never commit .env files or secrets to version control!


Persistent Data

Database Volume

The SQLite database is stored in a Docker volume for persistence:

volumes:
  school-clubs-data:
    driver: local

Location: /var/lib/docker/volumes/school-clubs-data/_data/

Backup Database

# Copy from container
docker compose exec app cat /data/data.db > backup.db

# Or directly from volume
docker run --rm -v school-clubs-data:/data \
  -v $(pwd):/backup alpine \
  cp /data/data.db /backup/backup.db

Restore Database

# Copy to container
docker compose cp backup.db app:/data/data.db

# Restart application
docker compose restart app

Traefik Integration

Traefik Configuration

The application uses Traefik for automatic HTTPS with Let's Encrypt.

Labels Explained:

labels:
  # Enable Traefik
  - "traefik.enable=true"

  # Route traffic from domain
  - "traefik.http.routers.school-clubs.rule=Host(`se.unilu.dev`)"

  # Use HTTPS entrypoint
  - "traefik.http.routers.school-clubs.entrypoints=websecure"

  # Enable TLS with Let's Encrypt
  - "traefik.http.routers.school-clubs.tls.certresolver=letsencrypt"

  # Forward to port 5000
  - "traefik.http.services.school-clubs.loadbalancer.server.port=5000"

Without Traefik

If not using Traefik, expose ports directly:

services:
  app:
    ports:
      - "80:5000"
    # Remove labels

Docker Commands

Build and Start

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

# Start without building
docker compose up -d

# Start specific service
docker compose up -d app

Stop and Remove

# Stop all services
docker compose stop

# Stop and remove containers
docker compose down

# Remove containers and volumes
docker compose down -v

View Logs

# All services
docker compose logs -f

# Specific service
docker compose logs -f app

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

Execute Commands

# Open shell in container
docker compose exec app /bin/bash

# Run Flask command
docker compose exec app flask seed-db

# Check Python version
docker compose exec app python --version

Inspect Containers

# List running containers
docker compose ps

# Container details
docker inspect school-clubs-app

# Resource usage
docker stats school-clubs-app

CI/CD with GitHub Actions

Automated Deployment

Changes pushed to dev branch automatically deploy via GitHub Actions.

Workflow File: .github/workflows/deploy.yml

name: Deploy to Server

on:
  push:
    branches: [dev]

jobs:
  deploy:
    runs-on: ubuntu-latest
    steps:
      - name: Deploy via SSH
        uses: appleboy/ssh-action@master
        with:
          host: ${{ secrets.HOST }}
          username: ${{ secrets.USERNAME }}
          key: ${{ secrets.SSH_KEY }}
          script: |
            cd /path/to/SE1
            git pull origin dev
            docker compose up -d --build

Required Secrets:

  • HOST: Server hostname or IP
  • USERNAME: SSH username
  • SSH_KEY: Private SSH key

Manual Deployment

# SSH to server
ssh user@server

# Navigate to project
cd /path/to/SE1

# Pull latest changes
git pull origin dev

# Rebuild and restart
docker compose up -d --build

Production Deployment

Security Checklist

  • Use strong SECRET_KEY from environment variable
  • Enable HTTPS with Traefik or reverse proxy
  • Restrict exposed ports
  • Use non-root user in container
  • Enable Docker security scanning
  • Configure firewall rules
  • Set up monitoring and logging
  • Regular database backups
  • Update base images regularly

Performance Optimization

Gunicorn Workers:

services:
  app:
    command: >
      gunicorn
      --bind 0.0.0.0:5000
      --workers 4
      --threads 2
      --timeout 60
      "school_clubs:create_app()"

Worker Formula: (2 × CPU cores) + 1

Resource Limits:

services:
  app:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 512M

Health Checks

services:
  app:
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

Monitoring

Container Logs

# View logs
docker compose logs -f app

# Save logs to file
docker compose logs app > app.log

Application Metrics

Consider adding monitoring tools:

  • Prometheus: Metrics collection
  • Grafana: Visualization dashboards
  • ELK Stack: Log aggregation and analysis

Example Prometheus Integration

services:
  app:
    environment:
      - PROMETHEUS_MULTIPROC_DIR=/tmp
    volumes:
      - prometheus-data:/tmp

  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    ports:
      - "9090:9090"

Troubleshooting

Container Won't Start

Check logs:

docker compose logs app

Common issues:

  • Missing environment variables
  • Port already in use
  • Invalid configuration
  • Database locked

Database Issues

Check database file:

docker compose exec app ls -lh /data/

Reset database:

docker compose down -v
docker compose up -d
docker compose exec app flask seed-db

Permission Issues

Fix volume permissions:

docker compose exec app chown -R 1000:1000 /data

Network Issues

Check network:

docker network ls
docker network inspect traefik-network

Recreate network:

docker network create traefik-network
docker compose up -d

Alternative Deployment

Docker Swarm

# Initialize swarm
docker swarm init

# Deploy stack
docker stack deploy -c docker-compose.yml school-clubs

# List services
docker stack services school-clubs

# Remove stack
docker stack rm school-clubs

Kubernetes

For Kubernetes deployment, create deployment and service manifests.

Example Deployment:

apiVersion: apps/v1
kind: Deployment
metadata:
  name: school-clubs
spec:
  replicas: 3
  selector:
    matchLabels:
      app: school-clubs
  template:
    metadata:
      labels:
        app: school-clubs
    spec:
      containers:
      - name: app
        image: school-clubs:latest
        ports:
        - containerPort: 5000
        env:
        - name: SECRET_KEY
          valueFrom:
            secretKeyRef:
              name: school-clubs-secret
              key: secret-key

Next Steps