Skip to main content

Overview

The repository ships with two Docker files:
  • Dockerfile — production image built on python:3.11-slim. Installs ODBC Driver 17 for SQL Server, Python dependencies, and runs Gunicorn with 4 workers.
  • docker-compose.yml — local development setup. Mounts a ./logs volume, forwards port 5000, and wires environment variables from a .env file.

Prerequisites

Quick start

1

Create a .env file

Copy the example environment file and update values as needed:
cp .env.example .env
At minimum, set SECRET_KEY and JWT_SECRET_KEY. The Azure SQL variables are optional — if omitted, the app falls back to SQLite.
2

Build and start the container

docker-compose up --build
The first build downloads the base image and installs ODBC Driver 17, which takes a few minutes. Subsequent builds use the layer cache.
3

Verify the API is running

curl http://localhost:5000/api/health
A healthy response returns HTTP 200 with application name and version.

Dockerfile

Dockerfile
# Use Python 3.11 slim image
FROM python:3.11-slim

# Set working directory
WORKDIR /app

# Set environment variables
ENV PYTHONDONTWRITEBYTECODE=1 \
    PYTHONUNBUFFERED=1 \
    FLASK_APP=run.py

# Install system dependencies for pyodbc and SQL Server
RUN apt-get update && apt-get install -y \
    curl \
    gnupg \
    unixodbc \
    unixodbc-dev \
    && curl https://packages.microsoft.com/keys/microsoft.asc | apt-key add - \
    && curl https://packages.microsoft.com/config/debian/11/prod.list > /etc/apt/sources.list.d/mssql-release.list \
    && apt-get update \
    && ACCEPT_EULA=Y apt-get install -y msodbcsql17 \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

# Copy requirements first for better caching
COPY requirements.txt .

# Install Python dependencies
RUN pip install --no-cache-dir --upgrade pip && \
    pip install --no-cache-dir -r requirements.txt

# Copy application code
COPY . .

# Create logs directory
RUN mkdir -p logs

# Expose port
EXPOSE 5000

# Health check
HEALTHCHECK --interval=30s --timeout=10s --start-period=40s --retries=3 \
    CMD curl -f http://localhost:5000/api/health || exit 1

# Run with gunicorn for production
CMD ["gunicorn", "--bind", "0.0.0.0:5000", "--workers", "4", "--timeout", "120", "run:app"]

docker-compose.yml

docker-compose.yml
version: '3.8'

services:
  api:
    build: .
    container_name: taskforge-api
    ports:
      - "5000:5000"
    environment:
      - FLASK_ENV=development
      - FLASK_APP=run.py
      - SECRET_KEY=${SECRET_KEY:-dev-secret-key-change-in-production}
      - JWT_SECRET_KEY=${JWT_SECRET_KEY:-jwt-secret-key-change-in-production}
      - AZURE_SQL_SERVER=${AZURE_SQL_SERVER}
      - AZURE_SQL_DATABASE=${AZURE_SQL_DATABASE:-taskforge_db}
      - AZURE_SQL_USER=${AZURE_SQL_USER}
      - AZURE_SQL_PASSWORD=${AZURE_SQL_PASSWORD}
      - AZURE_SQL_PORT=${AZURE_SQL_PORT:-1433}
      - CORS_ORIGINS=http://localhost:3000,http://localhost:5173
    volumes:
      - ./logs:/app/logs
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:5000/api/health"]
      interval: 30s
      timeout: 10s
      retries: 3
      start_period: 40s

networks:
  default:
    name: taskforge-network

Environment variables

Docker Compose reads variable values from your .env file using shell variable expansion (${VAR:-default}). The Azure SQL variables are forwarded directly to the container; if they are empty, the application uses the SQLite fallback.
The CORS_ORIGINS variable is hardcoded in docker-compose.yml to http://localhost:3000,http://localhost:5173 for local development. Override it in your .env if your frontend runs on a different port.

Useful commands

docker-compose build --no-cache

Volumes and persistence

The Compose file mounts ./logs from the project root into /app/logs inside the container:
volumes:
  - ./logs:/app/logs
Gunicorn access and error logs written to /app/logs are therefore persisted on the host. No database volume is configured — when running without Azure SQL, SQLite writes to taskforge.db inside the container and is lost when the container is removed. Mount an additional volume if you need to persist the SQLite file between container restarts.

Health check

Both the Dockerfile and docker-compose.yml configure a health check against the /api/health endpoint:
curl http://localhost:5000/api/health
The container is marked healthy after 40 seconds and checked every 30 seconds thereafter (3 retries, 10-second timeout). Docker Compose will show the health status in docker-compose ps.

Build docs developers (and LLMs) love