Legacy: Implementation Guides 15 min read Jun 06, 2026

Docker Deployment for Context Management Systems

Containerize and deploy your context management system using Docker and Docker Compose for consistent, scalable deployments.

Docker Deployment for Context Management Systems

Why Containerize Your Context Management System

Context management systems involve multiple interconnected services: your application server, a database, a cache, an embedding service, and often a monitoring stack. Without containerization, setting up this stack consistently across development, staging, and production environments is error-prone and time-consuming. Docker solves this by packaging each service with its dependencies into portable, reproducible containers that run identically everywhere.

Beyond consistency, containerization enables horizontal scaling of individual components. If your context retrieval API is CPU-bound while your database has spare capacity, you can scale the API containers independently. This granular scaling is essential as your context system scales to handle production workloads.

Containerized deployments reduce environment-related production incidents by an estimated 60-80%, because the exact same container image that passed testing is what runs in production. "Works on my machine" stops being a problem.

Step 1: Write Production-Ready Dockerfiles

A production Dockerfile is different from a development one. Production images should be small, secure, and optimized for startup time.

Multi-Stage Build for Python Context API

# Stage 1: Build dependencies
FROM python:3.11-slim AS builder

WORKDIR /app
COPY requirements.txt .
RUN pip install --no-cache-dir --prefix=/install \
    -r requirements.txt

# Stage 2: Production image
FROM python:3.11-slim

# Security: run as non-root user
RUN groupadd -r appuser && useradd -r -g appuser appuser

WORKDIR /app
COPY --from=builder /install /usr/local
COPY . .

# Remove unnecessary files
RUN rm -rf tests/ docs/ .git/ __pycache__/

USER appuser
EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/health')"

CMD ["gunicorn", "app:app", \
     "-b", "0.0.0.0:8000", \
     "-w", "4", \
     "-k", "uvicorn.workers.UvicornWorker", \
     "--access-logfile", "-"]

Dockerfile Best Practices

PracticeWhy It MattersImplementation
Multi-stage buildsReduces image size by 40-70%Separate build and runtime stages
Non-root userLimits container compromise blast radiusUSER appuser directive
Health checksEnables orchestrator self-healingHEALTHCHECK instruction in Dockerfile
.dockerignorePrevents secrets and unnecessary files in imageExclude .git, .env, tests, docs
Pinned base imagesPrevents unexpected changes from upstreamUse python:3.11.7-slim not python:3-slim
Layer orderingMaximizes build cache utilizationCopy requirements.txt before source code

The .dockerignore File

A proper .dockerignore is essential for security and build performance:

.git
.gitignore
.env
.env.*
__pycache__
*.pyc
tests/
docs/
*.md
.pytest_cache
.mypy_cache
docker-compose*.yml
Dockerfile*
.dockerignore

Step 2: Compose Your Full Stack

Docker Compose orchestrates your multi-container deployment. Here is a production-oriented compose file for a complete context management stack:

version: '3.8'

services:
  context-api:
    build:
      context: .
      dockerfile: Dockerfile
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://ctxuser:${DB_PASSWORD}@db:5432/context_db
      - REDIS_URL=redis://:${REDIS_PASSWORD}@redis:6379/0
      - EMBEDDING_SERVICE_URL=http://embeddings:8080
      - LOG_LEVEL=info
    depends_on:
      db:
        condition: service_healthy
      redis:
        condition: service_healthy
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 1G
        reservations:
          cpus: '0.5'
          memory: 256M
    restart: unless-stopped
    networks:
      - context-net

  db:
    image: pgvector/pgvector:pg16
    environment:
      - POSTGRES_DB=context_db
      - POSTGRES_USER=ctxuser
      - POSTGRES_PASSWORD=${DB_PASSWORD}
    volumes:
      - pg_data:/var/lib/postgresql/data
      - ./init-db:/docker-entrypoint-initdb.d
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U ctxuser -d context_db"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G
    restart: unless-stopped
    networks:
      - context-net

  redis:
    image: redis:7-alpine
    command: redis-server --requirepass ${REDIS_PASSWORD} --maxmemory 512mb --maxmemory-policy allkeys-lru
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "-a", "${REDIS_PASSWORD}", "ping"]
      interval: 10s
      timeout: 5s
      retries: 5
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 768M
    restart: unless-stopped
    networks:
      - context-net

  embeddings:
    image: ghcr.io/huggingface/text-embeddings-inference:latest
    command: --model-id BAAI/bge-base-en-v1.5 --port 8080
    deploy:
      resources:
        limits:
          cpus: '4.0'
          memory: 4G
    restart: unless-stopped
    networks:
      - context-net

volumes:
  pg_data:
  redis_data:

networks:
  context-net:
    driver: bridge

Environment Variables and Secrets

Never hardcode secrets in your compose file or Dockerfile. Use a .env file for local development and a proper secrets manager in production:

# .env file (never commit this)
DB_PASSWORD=your-strong-database-password
REDIS_PASSWORD=your-strong-redis-password
OPENAI_API_KEY=sk-your-api-key

For production deployments, use Docker secrets or integrate with your cloud provider's secrets manager (AWS Secrets Manager, GCP Secret Manager, HashiCorp Vault). See our zero-trust security guide for comprehensive secrets management strategies.

Step 3: Database Initialization

Use Docker's init script mechanism to set up your database schema automatically when the container first starts:

# init-db/01-extensions.sql
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS vector;

# init-db/02-schema.sql
CREATE TABLE IF NOT EXISTS contexts (
  id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
  user_id UUID NOT NULL,
  context_type VARCHAR(50) NOT NULL,
  content JSONB NOT NULL,
  embedding vector(1536),
  created_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  updated_at TIMESTAMP WITH TIME ZONE DEFAULT NOW(),
  expires_at TIMESTAMP WITH TIME ZONE,
  metadata JSONB DEFAULT '{}',
  is_active BOOLEAN DEFAULT true,
  version INTEGER DEFAULT 1
);

CREATE INDEX IF NOT EXISTS idx_contexts_user_type
  ON contexts(user_id, context_type);
CREATE INDEX IF NOT EXISTS idx_contexts_embedding
  ON contexts USING hnsw (embedding vector_cosine_ops);

Place these SQL files in the init-db/ directory. PostgreSQL will execute them in alphabetical order on first startup. For schema migrations after initial deployment, use a migration tool like Alembic or Flyway.

Step 4: Build, Test, and Run

With your Dockerfile, compose file, and init scripts in place, bring up the full stack:

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

# Verify all services are healthy
docker compose ps

# Check logs for startup issues
docker compose logs -f context-api

# Run a quick health check
curl http://localhost:8000/health

# Run integration tests against the stack
docker compose exec context-api python -m pytest tests/integration/

Development vs Production Compose Files

Use a compose override file for development-specific settings:

# docker-compose.override.yml (auto-loaded in dev)
services:
  context-api:
    build:
      target: builder  # use build stage with dev deps
    volumes:
      - .:/app  # mount source for hot-reload
    environment:
      - LOG_LEVEL=debug
    command: uvicorn app:app --reload --host 0.0.0.0 --port 8000

  db:
    ports:
      - "5432:5432"  # expose DB port for local tools

  redis:
    ports:
      - "6379:6379"  # expose Redis port for local tools

Step 5: Production Deployment Considerations

Logging

Configure centralized logging so you can aggregate logs from all containers in one place:

# In docker-compose.prod.yml
services:
  context-api:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "5"
        tag: "context-api"

For production, forward logs to a centralized system (ELK, Loki, CloudWatch) using a log driver or a sidecar container running Fluent Bit.

Networking and Security

  • Never expose database ports to the host in production. Only the API port should be accessible externally.
  • Use a reverse proxy (Nginx or Traefik) in front of your API for TLS termination, rate limiting, and request routing.
  • Isolate networks: Put your API and reverse proxy on a frontend network, and your API, database, and cache on a backend network. The database and cache should not be reachable from the frontend network.

Backup Strategy

Automate database backups within your Docker deployment:

# Add to docker-compose.prod.yml
  db-backup:
    image: prodrigestivill/postgres-backup-local
    environment:
      - POSTGRES_HOST=db
      - POSTGRES_DB=context_db
      - POSTGRES_USER=ctxuser
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - SCHEDULE=@daily
      - BACKUP_KEEP_DAYS=30
    volumes:
      - ./backups:/backups
    depends_on:
      - db
    networks:
      - context-net

Step 6: Moving Beyond Docker Compose

Docker Compose is excellent for single-host deployments and small-scale production. As your context system grows, you will need container orchestration for multi-host deployments, auto-scaling, and self-healing.

When to Move to Kubernetes

  • You need to scale beyond what a single host can handle
  • You require zero-downtime deployments with rolling updates
  • You need auto-scaling based on context retrieval load
  • Your organization already runs a Kubernetes cluster

Managed Container Alternatives

If you do not want to operate Kubernetes, managed container services provide a middle ground:

  • AWS ECS/Fargate: Run your Docker containers without managing servers. Integrates with RDS for PostgreSQL and ElastiCache for Redis.
  • Google Cloud Run: Fully managed serverless containers. Best for stateless API workloads with variable traffic.
  • Azure Container Apps: Built on Kubernetes but abstracts away cluster management. Good for teams wanting Kubernetes capabilities without operational overhead.

Before deploying to any production environment, validate your system's performance with the techniques in our load testing guide. Run load tests against your containerized stack to identify bottlenecks and establish performance baselines.

Monitoring Your Containerized Stack

Add Prometheus and Grafana to your compose stack for monitoring. Instrument your context API with Prometheus metrics (request count, latency histograms, error rates) and visualize them in Grafana. See our dashboard building guide for details on what metrics to track and how to set up alerting.

# Add to docker-compose.prod.yml
  prometheus:
    image: prom/prometheus:latest
    volumes:
      - ./prometheus.yml:/etc/prometheus/prometheus.yml
    networks:
      - context-net

  grafana:
    image: grafana/grafana:latest
    ports:
      - "3000:3000"
    volumes:
      - grafana_data:/var/lib/grafana
    networks:
      - context-net

Frequently Asked Questions

How do I handle database migrations in a containerized environment?

Run migrations as a separate one-off container or init container, not as part of your application startup. This prevents multiple application replicas from running migrations simultaneously. Use a tool like Alembic with a migration lock to ensure only one migration runs at a time: docker compose run --rm context-api alembic upgrade head.

Should I use Docker volumes or bind mounts for database storage?

Use named Docker volumes for database storage in production. They are managed by Docker, perform better than bind mounts on most platforms, and work correctly with file permissions. Bind mounts are appropriate for development (to mount source code for hot-reload) but not for production data persistence.

How do I update my containers in production without downtime?

With Docker Compose, use docker compose up -d --no-deps --build context-api to rebuild and restart only the API container. For true zero-downtime updates, you need a load balancer in front of multiple API replicas and a rolling update strategy, which is where Kubernetes or a managed container service becomes valuable.

What is the recommended image size for a context management API container?

Aim for under 200MB. A Python 3.11-slim base with typical context management dependencies (FastAPI, asyncpg, redis, httpx) produces images around 150-180MB with multi-stage builds. Larger images slow down deployments and increase attack surface. Use docker images and docker history to identify what is consuming space and optimize accordingly.

Sources & References

2
Docker Compose Documentation
Docker Documentation
3
pgvector Docker Images
pgvector Documentation
4
Prometheus Documentation
Prometheus Documentation
5
Hugging Face Text Embeddings Inference
Hugging Face Documentation

Tags

docker deployment devops tutorial