Skip to content

Docker Development

⏱️ Time: 15 minutes | 🎯 Goal: Run complete containerized stack locally

Run all 5 services (UI, API, 3 MCP servers) in Docker containers with Azure authentication.


Prerequisites

Required: - Docker Desktop installed and running - Azure CLI installed (az --version) - Azure logged in (az login) - Azure AI Foundry project with GPT-4 model deployed - Contributor or Owner role on Azure subscription (to create service principal)

Check before starting:

# Verify Docker
docker --version
docker ps  # Should not error

# Verify Azure CLI
az --version
az login
az account show

Quick Start

1. Create Service Principal

This project uses service principal authentication for Docker containers (not Azure CLI credentials):

./scripts/create-service-principal.sh

Why service principal instead of Azure CLI? - Smaller images (no +500MB Azure CLI binary needed) - Least-privilege security (not your broad Owner role) - Production-aligned (same pattern as Managed Identity) - Cross-platform (no .azure folder mounts)

See: ADR-038: Service Principal Least Privilege for full rationale.

What this does: - Creates service principal: loan-defenders-docker-dev - Assigns "Azure AI User" role (includes inference + agent creation in AI Foundry) - Adds credentials to .env automatically

Expected output:

βœ… Service Principal Created!
βœ… Credentials added to .env

Note: Service principal credentials are automatically added to your .env file.

2. Configure Environment

# Clone repository (if not already done)
git clone https://github.com/niksacdev/loan-defenders.git
cd loan-defenders

# Create .env file (if not already done)
cp .env.example .env

# Edit .env - Verify these were added by script:
# AZURE_TENANT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# AZURE_CLIENT_ID=xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx
# AZURE_CLIENT_SECRET=xxxxx~...
# AZURE_AI_PROJECT_ENDPOINT=https://your-resource.services.ai.azure.com/api/projects/your-project
# AZURE_AI_MODEL_DEPLOYMENT_NAME=your-deployment-name

3. Configure MCP Server URLs

Important: Update MCP server URLs for Docker networking:

# In .env, use Docker container names (NOT localhost)
MCP_APPLICATION_VERIFICATION_URL=http://loan-defenders-mcp-app-verification:8010/mcp
MCP_DOCUMENT_PROCESSING_URL=http://loan-defenders-mcp-doc-processing:8011/mcp
MCP_FINANCIAL_CALCULATIONS_URL=http://loan-defenders-mcp-financial-calc:8012/mcp

Why? Inside Docker containers, localhost refers to the container itself. Containers communicate via Docker network using service names.

4. Start Services

# Build and start all containers
./scripts/docker-dev.sh up

# Or manually:
docker-compose build
docker-compose up -d

⏱️ First time: 5-8 minutes (image builds)
⏱️ Subsequent: 30-40 seconds (health checks)

Services started: - loan-defenders-ui (port 8080) - loan-defenders-api (port 8000) - loan-defenders-mcp-app-verification (port 8010) - loan-defenders-mcp-doc-processing (port 8011) - loan-defenders-mcp-financial-calc (port 8012)

5. Verify Deployment

# Check container status
docker-compose ps

# Should see all 5 containers "Up" and "healthy"

# Open in browser
open http://localhost:8080  # macOS
# or navigate to http://localhost:8080

Test it: 1. Click "Start Your Loan Application" 2. Fill in test data 3. Submit and watch agents process 4. Verify loan decision (30-60 seconds)


Managing Containers

# View logs (all services)
docker-compose logs -f

# View logs (specific service)
docker-compose logs -f api
docker-compose logs -f ui

# Check container health
docker-compose ps

# Restart single service
docker-compose restart api

# Stop all services
docker-compose down

# Stop and remove volumes
docker-compose down -v

Helper script commands:

# Start
./scripts/docker-dev.sh up

# View logs
./scripts/docker-dev.sh logs
./scripts/docker-dev.sh logs api  # Specific service

# Stop
./scripts/docker-dev.sh down

# Rebuild after code changes
./scripts/docker-dev.sh rebuild

# Complete reset
./scripts/docker-dev.sh clean

Development Workflow

Code Changes

After changing code:

# Stop containers
docker-compose down

# Rebuild specific service
docker-compose build api

# Or rebuild all
docker-compose build

# Restart
docker-compose up -d

Faster rebuild:

./scripts/docker-dev.sh rebuild

Testing Container Connectivity

# Test API from host
curl http://localhost:8000/health

# Test MCP servers from host
curl http://localhost:8010/health
curl http://localhost:8011/health
curl http://localhost:8012/health

# Test MCP connectivity from API container
docker exec loan-defenders-api curl http://loan-defenders-mcp-app-verification:8010/health

Authentication

Service principal authentication for containers:

This project uses service principal authentication for Docker containers (environment variables from .env).

How it works: 1. You run ./scripts/create-service-principal.sh 2. Script creates service principal with least-privilege role 3. Credentials added to .env file 4. Docker containers read from .env: - AZURE_TENANT_ID - AZURE_CLIENT_ID - AZURE_CLIENT_SECRET 5. DefaultAzureCredential uses these environment variables

Why service principal? - Smaller images (no Azure CLI binary needed) - Least-privilege role (inference only) - Same pattern as production (environment variables) - Script automates creation and .env setup

Alternative: Azure CLI mount is possible (requires .azure folder mount and CLI binary in containers, +500MB per image). This project chose service principal for simplicity and security. See ADR-038.

Verify authentication:

# Check API logs for successful auth
docker-compose logs api | grep -i "authentication"

# Should see: "Using EnvironmentCredential for authentication"

Troubleshooting

Container Exits Immediately

Check logs:

docker-compose logs api

Common causes: - Missing .env file β†’ Copy from .env.example - Invalid Azure credentials β†’ Check .env values - Missing environment variables β†’ Compare with .env.example - Build failed β†’ Run docker-compose build --no-cache api

Agent Processing Hangs

Error: Stuck at "Intake Agent" or agents not progressing

Cause: MCP server URLs pointing to localhost instead of Docker container names

Fix:

# Check .env uses container names (NOT localhost)
grep MCP_ .env

# Should show:
# MCP_APPLICATION_VERIFICATION_URL=http://loan-defenders-mcp-app-verification:8010/mcp
# NOT: http://localhost:8010/mcp

# After fixing .env, restart:
docker-compose restart api

Port Already in Use

Error: port is already allocated

# Find what's using the port
lsof -i :8000  # or :8080, :8010, etc.

# Kill the process
kill -9 <PID>

# Or change port in docker-compose.yml:
ports:
  - "8001:8000"  # External:Internal

Authentication Fails

Error: DefaultAzureCredential failed to retrieve a token

Cause: Service principal credentials missing or incorrect in .env

Fix:

# Verify .env has service principal credentials
cat .env | grep AZURE_

# Should show:
# AZURE_TENANT_ID=xxxxxxxx-...
# AZURE_CLIENT_ID=xxxxxxxx-...
# AZURE_CLIENT_SECRET=xxxxx~...
# AZURE_AI_PROJECT_ENDPOINT=https://...
# AZURE_AI_MODEL_DEPLOYMENT_NAME=...

# If missing, re-run service principal script:
./scripts/create-service-principal.sh

# Restart containers
docker-compose restart

Connection Refused

Error: UI can't connect to API

Check:

# 1. Verify API is running
docker-compose ps api

# 2. Test API endpoint
curl http://localhost:8000/health

# 3. Check API logs
docker-compose logs api

# 4. Verify CORS in .env
grep CORS .env

# Should include: http://localhost:8080

Clean Up

Stop Services

# Graceful shutdown
docker-compose down

# Remove volumes too
docker-compose down -v

Complete Reset

# Remove containers, images, volumes
docker-compose down -v
docker system prune -af

# Rebuild from scratch
docker-compose build --no-cache
docker-compose up -d

What's Deployed

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚ Docker Container Network                β”‚
β”‚                                         β”‚
β”‚  β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”   β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”          β”‚
β”‚  β”‚ UI       │──▢│ API      β”‚          β”‚
β”‚  β”‚ :8080    β”‚   β”‚ :8000    β”‚          β”‚
β”‚  β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜   β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜          β”‚
β”‚                       β”‚                β”‚
β”‚                       β–Ό                β”‚
β”‚              β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”         β”‚
β”‚              β”‚ MCP Servers  β”‚         β”‚
β”‚              β”‚ :8010-8012   β”‚         β”‚
β”‚              β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜         β”‚
β”‚                       β”‚                β”‚
β”‚                       β–Ό                β”‚
β”‚              Azure AI Foundry         β”‚
β”‚              (via Service Principal)  β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Container networking: - Containers use Docker's internal network - Internal communication: service names (e.g., loan-defenders-api) - External access: localhost (ports mapped by docker-compose)


Why Docker?

Use Docker when: - Testing full system integration - Validating production-like environment - Need reproducible builds - Testing deployment before Azure

Use local dev when: - Daily coding (faster iteration) - Debugging with IDE - Hot-reload development


What's Next?

Deploy to Azure: - Azure Deployment - Production deployment

Learn the System: - System Architecture - How it works - Docker Architecture - Container design

Development: - Local Development - Faster iteration

Need Help? - Troubleshooting Guide - Common issues - GitHub Discussions - Ask questions


πŸŽ‰ Docker environment ready! All services containerized and tested.