Claude Skills Guide

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:

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.