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