AI Intermediate: Infrastructure & Agentic SystemsDay 7 of 30 · Week 1: Bare-Metal Infrastructure & Advanced Containerization
Course outline▾

Week 2 · Kubernetes Foundation for MLOps

  1. Day 8K3d & Lightweight Kubernetes: Running Multi-Node Clusters on 16GB of RAM
  2. Day 9Kubernetes Core for Machine Learning: Pods, Deployments, and Headless Services
  3. Day 10Persistent Volumes (PV/PVC): Managing Heavy Model Weights and Vector Stores
  4. Day 11StatefulSets for Vector Databases: Deploying Milvus or Qdrant in Kubernetes
  5. Day 12Auto-Scaling AI Inference: Using Horizontal Pod Autoscalers (HPA) for Spiky API Traffic
  6. Day 13Helm Charts: Packaging, Versioning, and Updating AI Deployments
  7. Day 14Week 2 Capstone: Deploying a Highly Available Local AI Application on K3d

Week 3 · High-Performance Networking & Security Mesh

  1. Day 15AI Network Bottlenecks: Transitioning from iptables to eBPF
  2. Day 16Cilium & eBPF: Sub-Millisecond Routing for Heavy AI Microservices
  3. Day 17Service Mesh 101: Installing Istio over Your Local Cluster
  4. Day 18Envoy Proxy as an AI Gateway: Load Balancing and Terminating LLM Prompts
  5. Day 19Zero-Trust mTLS: Encrypting Traffic Between Autonomous Agents
  6. Day 20API Rate Limiting & Egress Control: Preventing Agents from Exfiltrating Data
  7. Day 21Distributed Tracing: Debugging Agent Workflows with Jaeger and OpenTelemetry

Week 4 · Enterprise Agents & CI/CD Workflows

  1. Day 22Introduction to MLOps: Bridging AI Development with Continuous Integration
  2. Day 23GitOps with ArgoCD: Automated, Self-Healing AI Deployments
  3. Day 24Building Ephemeral AI Sandboxes: Secure Execution Environments for Agent Code
  4. Day 25Multi-Agent Orchestration Frameworks: Exploring Mastra, LangGraph, and CrewAI
  5. Day 26Tool-Calling in Production: Connecting Agents Securely to Internal Databases
  6. Day 27MLOps Guardrails: Evaluating Agents and Preventing Hallucinations in Real-Time
  7. Day 28Continuous Training Pipelines: Connecting Data Ingestion to Automated Model Retraining
  8. Day 29Managing Technical Debt and Infrastructure Drift in Enterprise AI
  9. Day 30The AI-Native Platform: Scaling from a Sandbox Environment to Global Cloud Architecture
Open the course page →

Day 7: Docker Compose — Architecting a Multi-Container Database and Inference Stack Locally

2026-11-13 · 8 min read

A production-ready RAG platform is not one script. It needs several specialised services on a shared internal network. Docker Compose is the declarative, infrastructure-as-code way to assemble them on a single host, and it closes Week 1 by pulling together everything so far.

The topology

THE STACK ON ONE HOSTHost port 80from the userai-gatewayFastAPI/Streamlit :8000bridge network: ai-internal-netvector-storeQdrant :6333inference-engineOllama :11434volume/opt/data/vectorvolume/opt/data/modelsOnly port 80 is published. Qdrant and Ollama are unreachable from outside.
FIG 1 · A gateway publishes one port. The vector store and inference engine sit on a private bridge network, each with its own persistent volume.

Three services make up the stack:

  • State store: a Qdrant vector database holding the embeddings.
  • Inference engine: Ollama (or vLLM) running the model.
  • Application and gateway: the FastAPI or Streamlit service from Day 6 that orchestrates retrieval and generation.

The Compose file

name: ai-stack

networks:
  ai-internal-net:
    driver: bridge

volumes:
  qdrant_storage:
  ollama_storage:

services:
  vector-store:
    image: qdrant/qdrant:v1.9.0
    networks: [ai-internal-net]
    volumes:
      - qdrant_storage:/qdrant/storage
    healthcheck:
      # A TCP probe avoids needing curl in the image. Adjust to your image version.
      test: ["CMD-SHELL", "bash -c ':> /dev/tcp/127.0.0.1/6333' || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 10

  inference-engine:
    image: ollama/ollama:latest          # pin a tested tag in production
    networks: [ai-internal-net]
    volumes:
      - ollama_storage:/root/.ollama
    deploy:
      resources:
        limits:
          memory: 10G                    # leave about 6 GB of a 16 GB host free
    healthcheck:
      test: ["CMD", "ollama", "list"]
      interval: 15s
      timeout: 10s
      retries: 10

  ai-gateway:
    build: ./gateway
    networks: [ai-internal-net]
    ports:
      - "80:8000"                        # the only port published to the host
    environment:
      VECTOR_DB_HOST: vector-store
      VECTOR_DB_PORT: "6333"
      LLM_HOST: inference-engine
      LLM_PORT: "11434"
    depends_on:
      vector-store:
        condition: service_healthy
      inference-engine:
        condition: service_healthy

Start it with docker compose up -d --build, check it with docker compose ps, and stop it with docker compose down. Add -v to down only when you want the volumes deleted too.

Three principles

1. Service discovery by name. Inside a Compose network every service name is a DNS name. Use it, and never hard-code IP addresses or localhost: container IPs change on restart, and localhost points at the calling container itself.

USE SERVICE NAMES, NEVER localhost OR IPsWRONGVECTOR_DB_HOST=localhostVECTOR_DB_HOST=172.18.0.3localhost is the gateway container itself;container IPs change on every restartRIGHTVECTOR_DB_HOST=vector-storeLLM_HOST=inference-engineCompose's internal DNS resolveseach service name to its containerhttp://inference-engine:11434http://vector-store:6333
FIG 2 · Inside a Compose network, each service name is a DNS name. Hard-coded IPs break on restart, and localhost points at the calling container itself.

2. Healthchecks and dependency ordering. depends_on with condition: service_healthy makes the gateway wait until both services actually pass their checks, so it never queries a database or model server that is still starting.

HEALTHCHECKS AND depends_onvector-storestartinginference-engineloading modelai-gatewaywaiting for service_healthyboth healthydepends_on: vector-store: { condition: service_healthy } inference-engine: { condition: service_healthy }
FIG 3 · The gateway waits until the vector store and the inference engine pass their healthchecks, so it never queries a service that is still starting.

3. Volume decoupling. Keep persistent data in named volumes, outside the containers. You can then upgrade, restart or rebuild the stack without losing the vector index or re-downloading model weights.

WHAT LIVES WHERE: VOLUMES AND LIMITSContainersdisposableNamed volumespersistentHost RAM 16 GBinference-engine 10G caprebuild any timeqdrant_storage · ollama_storageleaves 6 GB for the hostvolumes: qdrant_storage: ollama_storage:deploy: resources: limits: memory: 10Gkeeps one model from starving the hostUpdate, restart or rebuild containers without losing the index or the weights.
FIG 4 · Persistent data lives in named volumes, so containers stay disposable. A memory limit on the inference engine protects the host.

Two practical notes. The memory limit under deploy.resources is honoured by current docker compose, but check it with docker stats on your setup. And the healthcheck commands depend on what each image ships, so confirm they work with docker compose ps showing healthy.

That completes Week 1: lean images, GPU access, quantized weights, dedicated runtimes, a validated API and a stack that starts in the right order. Next week moves it all onto Kubernetes, starting with a lightweight cluster on a 16 GB machine.

Run it yourself

This is the exact example from the video. Docker Compose itself reads this file, and a short check prints what it found. Nothing is started, and the gateway image is a placeholder.

name: ai-stack
services:
  vector-store:
    image: qdrant/qdrant:v1.9.0
    volumes: [qdrant_data:/qdrant/storage]
  inference-engine:
    image: ollama/ollama:latest
    volumes: [ollama_data:/root/.ollama]
  ai-gateway:
    image: ai-gateway:dev
    ports: ["80:8000"]
    environment:
      VECTOR_DB_HOST: vector-store
      LLM_HOST: inference-engine
    depends_on: [vector-store, inference-engine]
volumes: {qdrant_data: {}, ollama_data: {}}

Helper file check.py, used by the run:

import json, subprocess
raw = subprocess.check_output(["docker", "compose", "config", "--format", "json"])
c = json.loads(raw)
svc = c["services"]
print("services:", len(svc))
for n, s in svc.items():
    for p in s.get("ports", []):
        print("published:", n, p["published"] + "->" + str(p["target"]))
g = svc["ai-gateway"]
print("db host:", g["environment"]["VECTOR_DB_HOST"])
for d in sorted(g["depends_on"]):
    print("waits for:", d)
print("volumes:", ", ".join(sorted(c["volumes"])))

Run it:

python check.py

Output I got while recording the video:

services: 3
published: ai-gateway 80->8000
db host: vector-store
waits for: inference-engine
waits for: vector-store
volumes: ollama_data, qdrant_data

Coming Up Next

Day 8: K3d and lightweight Kubernetes: running multi-node clusters on 16 GB of RAM.

#DockerCompose #Microservices #RAG #SystemDesign #DevOps #VectorDatabase #AIInfrastructure #SoftwareEngineering