Skip to main content

Overview

Docker provides a consistent, portable deployment environment for SKU Semantic Search. The included Docker Compose configuration sets up both the FastAPI application and PostgreSQL database with a single command.

Prerequisites

  • Docker: Version 20.10 or higher
  • Docker Compose: Version 2.0 or higher
# Install Docker
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# Add user to docker group (optional, avoids sudo)
sudo usermod -aG docker $USER
newgrp docker

# Verify installation
docker --version
docker-compose --version

Docker Compose configuration

The project includes a complete docker-compose.yml file:
services:
  api:
    build: .
    container_name: retail_rag_api
    ports:
      - "8000:8000"
    volumes:
      - .:/app  # Sync local code with container for live reloading
    environment:
      - DATABASE_URL=postgresql://postgres:db_pass@db:5435/retail_rag
      - GEMINI_API_KEY=${GEMINI_API_KEY}
      - JWT_SECRET=una_clave_secreta_muy_segura_para_el_mvp
    depends_on:
      db:
        condition: service_healthy
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --reload

  db:
    image: pgvector/pgvector:pg16
    container_name: retail_rag_db
    ports:
      - "5435:5432"
    environment:
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=db_pass
      - POSTGRES_DB=retail_rag
    volumes:
      - postgres_data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres"]
      interval: 10s
      timeout: 5s
      retries: 5

volumes:
  postgres_data:

Configuration breakdown

Build context: . (current directory)
  • Uses the included Dockerfile to build the application image
Port mapping: 8000:8000
  • Exposes FastAPI on port 8000 of the host machine
Volume mount: .:/app
  • Syncs local code changes into the container
  • Enables live reloading during development
Environment variables:
  • DATABASE_URL: Uses db as hostname (Docker Compose service name)
  • GEMINI_API_KEY: Loaded from host .env file
  • JWT_SECRET: Hardcoded for demo (use secrets in production)
Dependencies:
  • depends_on with condition: service_healthy ensures PostgreSQL is ready before starting the API
Command:
  • --reload enables auto-restart on code changes (remove in production)
Image: pgvector/pgvector:pg16
  • Pre-built image with PostgreSQL 16 and pgvector extension
Port mapping: 5435:5432
  • External port 5435 avoids conflicts with local PostgreSQL
  • Internal port 5432 is standard PostgreSQL
Environment variables:
  • POSTGRES_USER: Superuser name
  • POSTGRES_PASSWORD: Superuser password (change in production!)
  • POSTGRES_DB: Default database to create
Volume: postgres_data
  • Named volume persists data across container restarts
  • Stored in Docker’s volume directory
Health check:
  • Runs pg_isready every 10 seconds
  • API service waits until this returns healthy
postgres_data:
  • Named volume for database persistence
  • Survives docker-compose down
  • Deleted only with docker-compose down -v
Location:
# View volume details
docker volume inspect sku-semantic-search_postgres_data

Dockerfile

The application uses a multi-stage Dockerfile for efficient builds (Dockerfile:1):
# Use official lightweight Python image
FROM python:3.11-slim

# Prevent Python from writing .pyc files and force unbuffered output
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Set working directory
WORKDIR /app

# Install system dependencies for PostgreSQL
RUN apt-get update \
    && apt-get install -y gcc libpq-dev \
    && apt-get clean \
    && rm -rf /var/lib/apt/lists/*

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

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

# Copy project code
COPY . .

# Expose FastAPI port
EXPOSE 8000

# Default startup command
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]
Optimization features:
  • Slim base image: python:3.11-slim reduces image size
  • Layer caching: Copying requirements.txt first allows Docker to cache dependencies
  • No cache: --no-cache-dir reduces image size by not storing pip cache
  • System dependencies: gcc and libpq-dev are needed to compile psycopg2-binary

Deployment steps

1

Clone repository

git clone https://github.com/your-username/sku-semantic-search.git
cd sku-semantic-search
2

Configure environment variables

Create a .env file in the project root:
# .env
GEMINI_API_KEY=your_gemini_api_key_here
ANTHROPIC_API_KEY=your_anthropic_api_key_here
Docker Compose automatically loads .env files. The GEMINI_API_KEY variable is passed to the container using ${GEMINI_API_KEY} syntax.
3

Build and start services

docker-compose up -d
This command:
  1. Builds the API image from the Dockerfile
  2. Pulls the pgvector PostgreSQL image
  3. Creates a network for service communication
  4. Starts both containers in detached mode
First run: Building the image takes 2-5 minutes depending on your connection.
4

Verify deployment

# Check container status
docker-compose ps
Expected output:
NAME                IMAGE                           STATUS
retail_rag_api      sku-semantic-search-api         Up
retail_rag_db       pgvector/pgvector:pg16          Up (healthy)
Both containers should show “Up” status.
5

View logs

# All services
docker-compose logs -f

# API only
docker-compose logs -f api

# Database only
docker-compose logs -f db
Press Ctrl+C to stop following logs.
6

Seed the database

docker-compose exec api python seed_data.py
This runs the seed script inside the API container, populating the database with sample products.
7

Access the API

The API is now available at:

Docker Compose commands

# Start in foreground (see logs)
docker-compose up

# Start in background (detached)
docker-compose up -d

# Rebuild images before starting
docker-compose up --build

Production deployment

For production environments, modify the configuration:

Production docker-compose.yml

services:
  api:
    build:
      context: .
      dockerfile: Dockerfile.prod  # Production-optimized Dockerfile
    restart: always
    ports:
      - "8000:8000"
    environment:
      - DATABASE_URL=postgresql://sku_user:${DB_PASSWORD}@db:5432/sku_prod
      - GEMINI_API_KEY=${GEMINI_API_KEY}
      - ANTHROPIC_API_KEY=${ANTHROPIC_API_KEY}
      - JWT_SECRET=${JWT_SECRET}
    depends_on:
      db:
        condition: service_healthy
    command: uvicorn app.main:app --host 0.0.0.0 --port 8000 --workers 4
    # Remove volume mount for code sync

  db:
    image: pgvector/pgvector:pg16
    restart: always
    ports:
      - "5432:5432"  # Use standard port
    environment:
      - POSTGRES_USER=sku_user
      - POSTGRES_PASSWORD=${DB_PASSWORD}
      - POSTGRES_DB=sku_prod
    volumes:
      - postgres_data:/var/lib/postgresql/data
      - ./backups:/backups  # Backup directory
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U sku_user"]
      interval: 30s
      timeout: 10s
      retries: 3

volumes:
  postgres_data:
Key changes for production:
  • restart: always - Containers restart on failure
  • --workers 4 - Multiple Uvicorn workers for concurrency
  • Removed --reload - No auto-restart on file changes
  • Removed code volume mount - Bakes code into image
  • All secrets loaded from environment (not hardcoded)
  • Backup volume for database dumps

Production Dockerfile

# Build stage
FROM python:3.11-slim AS builder

WORKDIR /app

RUN apt-get update && apt-get install -y gcc libpq-dev

COPY requirements.txt .
RUN pip install --user --no-cache-dir -r requirements.txt

# Runtime stage
FROM python:3.11-slim

WORKDIR /app

RUN apt-get update && apt-get install -y libpq5 && rm -rf /var/lib/apt/lists/*

# Copy dependencies from builder
COPY --from=builder /root/.local /root/.local

# Copy application
COPY app ./app

ENV PATH=/root/.local/bin:$PATH

EXPOSE 8000

CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000", "--workers", "4"]
Multi-stage benefits:
  • Smaller final image (build tools not included)
  • Faster deployments
  • Better security (no compilers in production)

Reverse proxy with Nginx

For production, place Nginx in front of the API:
services:
  nginx:
    image: nginx:alpine
    restart: always
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx.conf:/etc/nginx/nginx.conf:ro
      - ./ssl:/etc/nginx/ssl:ro
    depends_on:
      - api

  api:
    # ... existing config
    expose:
      - "8000"  # Not published to host
nginx.conf:
upstream fastapi {
    server api:8000;
}

server {
    listen 80;
    server_name your-domain.com;

    location / {
        proxy_pass http://fastapi;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
    }
}

Monitoring and logging

View container metrics

# Real-time resource usage
docker stats retail_rag_api retail_rag_db

# Container inspection
docker inspect retail_rag_api

Centralized logging

Configure Docker to use a logging driver:
services:
  api:
    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
For production, consider:
  • ELK Stack: Elasticsearch, Logstash, Kibana
  • Loki: Grafana Loki for log aggregation
  • CloudWatch: AWS CloudWatch Logs

Troubleshooting

Error: Bind for 0.0.0.0:8000 failed: port is already allocatedSolution:
# Find process using port 8000
sudo lsof -i :8000  # Linux/macOS
netstat -ano | findstr :8000  # Windows

# Kill the process or change port in docker-compose.yml
ports:
  - "8001:8000"  # Use 8001 externally
Error: psycopg2.OperationalError: could not connect to serverSolution:
  1. Check database is healthy: docker-compose ps
  2. View database logs: docker-compose logs db
  3. Verify DATABASE_URL uses db as hostname (not localhost)
  4. Wait for health check: API starts only when DB is ready
Error: During docker-compose up --buildSolution:
  1. Check Dockerfile syntax
  2. Ensure requirements.txt exists and is valid
  3. Clear build cache: docker-compose build --no-cache
  4. Check disk space: docker system df
Symptoms: Code changes don’t appear in running containerSolution:
  1. Verify volume mount exists: - .:/app
  2. Check --reload flag is present in command
  3. For Dockerfile changes, rebuild: docker-compose up --build
  4. Restart services: docker-compose restart api

Next steps

Environment setup

Configure environment variables for Docker

Database setup

Optimize PostgreSQL for production

Architecture overview

Understand the deployed system architecture

Quickstart

Test the deployed API

Build docs developers (and LLMs) love