Claude Code Docker Compose API Tutorial Guide
Docker Compose has become an essential tool for developers working with APIs. When combined with Claude Code, it creates a powerful workflow for building, testing, and deploying API services. This comprehensive guide will walk you through setting up a complete API development environment using Docker Compose and Claude Code.
Understanding the Docker Compose API Workflow
Docker Compose allows you to define and run multi-container applications. For API development, you’ll typically have containers for your API server, database, cache, and potentially other services like message queues or authentication services.
Claude Code can interact with your Docker Compose setup to help you:
- Generate Docker Compose configuration files
- Debug container issues
- Manage service lifecycle
- Write integration tests
- Document your API infrastructure
Setting Up Your First API Stack
Let’s create a practical example of a REST API stack using Docker Compose. We’ll build a simple TODO API with Node.js, PostgreSQL, and Redis.
Creating the Docker Compose Configuration
First, create a docker-compose.yml file in your project root:
version: '3.8'
services:
api:
build: .
ports:
- "3000:3000"
environment:
- DATABASE_URL=postgresql://user:password@db:5432/todos
- REDIS_URL=redis://cache:6379
depends_on:
- db
- cache
volumes:
- .:/app
- /app/node_modules
command: npm run dev
db:
image: postgres:15-alpine
environment:
- POSTGRES_USER=user
- POSTGRES_PASSWORD=password
- POSTGRES_DB=todos
volumes:
- postgres_data:/var/lib/postgresql/data
ports:
- "5432:5432"
cache:
image: redis:7-alpine
ports:
- "6379:6379"
volumes:
- redis_data:/data
volumes:
postgres_data:
redis_data:
This configuration sets up three services: your Node.js API, PostgreSQL database, and Redis cache. The depends_on ensures services start in the correct order.
Creating the API Service
Create a simple Express.js API to work with this stack. Here’s a basic setup:
const express = require('express');
const { Pool } = require('pg');
const redis = require('redis');
const app = express();
const pool = new Pool({ connectionString: process.env.DATABASE_URL });
const redisClient = redis.createClient({ url: process.env.REDIS_URL });
app.use(express.json());
// Get all todos
app.get('/api/todos', async (req, res) => {
try {
const cached = await redisClient.get('todos:all');
if (cached) return res.json(JSON.parse(cached));
const result = await pool.query('SELECT * FROM todos ORDER BY created_at DESC');
await redisClient.set('todos:all', JSON.stringify(result.rows), { EX: 60 });
res.json(result.rows);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
// Create a todo
app.post('/api/todos', async (req, res) => {
const { title } = req.body;
try {
const result = await pool.query(
'INSERT INTO todos (title) VALUES ($1) RETURNING *',
[title]
);
await redisClient.del('todos:all');
res.status(201).json(result.rows[0]);
} catch (err) {
res.status(500).json({ error: err.message });
}
});
app.listen(3000, () => console.log('API running on port 3000'));
Working with Claude Code
Claude Code can help you at every stage of your Docker Compose API development workflow. Here are practical ways to leverage it:
Generating Configuration Files
When starting a new project, ask Claude Code to generate appropriate Docker configurations:
Create a Docker Compose file for a Python FastAPI application with PostgreSQL,
MongoDB, and Celery for background tasks. Include health checks and proper
volume mounting.
Claude will generate a complete configuration tailored to your requirements.
Debugging Container Issues
When containers fail to start or behave unexpectedly, Claude Code can help analyze logs and identify problems. Provide the output of docker-compose logs and ask for debugging assistance.
Writing Tests
Claude Code excels at writing integration tests for your API:
const request = require('supertest');
const { app } = require('../src/index');
describe('TODO API', () => {
beforeAll(async () => {
// Setup test database
await pool.query('DELETE FROM todos');
});
it('should create a new todo', async () => {
const response = await request(app)
.post('/api/todos')
.send({ title: 'Test todo' })
.expect(201);
expect(response.body).toHaveProperty('id');
expect(response.body.title).toBe('Test todo');
});
it('should return all todos', async () => {
await request(app).post('/api/todos').send({ title: 'Todo 1' });
const response = await request(app)
.get('/api/todos')
.expect(200);
expect(Array.isArray(response.body)).toBe(true);
});
});
Best Practices for Docker Compose API Development
Follow these recommendations for efficient API development with Docker Compose:
Use Health Checks
Always define health checks for your services:
services:
db:
image: postgres:15-alpine
healthcheck:
test: ["CMD-SHELL", "pg_isready -U user -d todos"]
interval: 5s
timeout: 5s
retries: 5
api:
depends_on:
db:
condition: service_healthy
This ensures your API container waits until the database is ready before starting.
Implement Proper Environment Management
Use environment files for sensitive configuration:
# .env file
DATABASE_URL=postgresql://user:password@localhost:5432/dev
API_KEY=your-secret-key
Reference them in your compose file:
services:
api:
env_file:
- .env
Use Named Volumes for Development
Named volumes persist data across container restarts:
volumes:
postgres_data:
driver: local
Optimize for Development Speed
Use volume mounting and live reload:
services:
api:
volumes:
- .:/app
- /app/node_modules
environment:
- NODE_ENV=development
Managing Multiple Environments
As your project grows, you’ll need different configurations for development, staging, and production:
Development Override
Create a docker-compose.override.yml for local development:
version: '3.8'
services:
api:
build:
context: .
target: development
ports:
- "3000:3000"
- "9229:9229"
environment:
- DEBUG=true
This automatically merges with your base configuration when running docker-compose up.
Production Configuration
Create docker-compose.prod.yml for production:
version: '3.8'
services:
api:
build:
context: .
target: production
restart: unless-stopped
ports:
- "3000:3000"
logging:
driver: "json-file"
options:
max-size: "10m"
max-file: "3"
Deploy with: docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d
Testing Your API Stack
Automated testing is crucial for reliable API development. Here’s a testing workflow:
Unit Tests
Test individual functions and components in isolation:
// tests/unit/todoService.test.js
const { createTodo, getTodos } = require('../../src/services/todoService');
describe('Todo Service', () => {
describe('createTodo', () => {
it('should create a todo with valid input', async () => {
const result = await createTodo({ title: 'New Todo' });
expect(result).toHaveProperty('id');
});
});
});
Integration Tests
Test API endpoints with a running database:
// tests/integration/api.test.js
describe('API Integration Tests', () => {
let api;
beforeAll(async () => {
api = await startTestServer();
});
afterAll(async () => {
await api.stop();
});
it('should handle concurrent requests', async () => {
const requests = Array(10).fill().map(() =>
api.post('/api/todos').send({ title: 'Concurrent Todo' })
);
const results = await Promise.all(requests);
expect(results.every(r => r.status === 201)).toBe(true);
});
});
End-to-End Tests
Simulate real user scenarios:
// tests/e2e/userFlow.test.js
const { test, expect } = require('@playwright/test');
test('complete todo workflow', async ({ page }) => {
await page.goto('http://localhost:3000');
// Create todo
await page.fill('[data-testid="todo-input"]', 'Learn Docker Compose');
await page.click('[data-testid="submit-btn"]');
// Verify creation
await expect(page.locator('.todo-item')).toContainText('Learn Docker Compose');
// Complete todo
await page.click('.todo-item input[type="checkbox"]');
await expect(page.locator('.todo-item')).toHaveClass(/completed/);
});
Deployment Considerations
When deploying your Docker Compose API stack to production:
Use Docker Swarm or Kubernetes
For production workloads, consider orchestrating with Docker Swarm or Kubernetes. Docker Compose files can often be converted to Kubernetes manifests using tools like kompose.
Implement Monitoring
Add monitoring to track API performance:
services:
api:
volumes:
- /var/run/docker.sock:/var/run/docker.sock
prometheus:
image: prom/prometheus
volumes:
- ./prometheus.yml:/etc/prometheus/prometheus.yml
Set Up Log Aggregation
Centralize logs for debugging:
services:
api:
logging:
driver: "fluentd"
options:
fluentd-address: "localhost:24224"
tag: "api.{{.Name}}"
Conclusion
Combining Docker Compose with Claude Code creates a powerful development environment for building APIs. Docker Compose handles the complexity of multi-container applications, while Claude Code assists with configuration generation, debugging, testing, and documentation.
Start with simple setups and gradually add complexity as your application grows. Remember to implement health checks, proper environment management, and comprehensive testing from the beginning. With these practices in place, you’ll have a robust foundation for API development that scales with your project.