Docker Deployment

Deploy Agentfield agents and control plane using Docker

Docker Deployment

Deploy Agentfield agents and control plane using Docker

Overview

Docker provides a consistent deployment environment for Agentfield agents and the control plane. This guide covers both standalone agent deployment and full stack deployment with docker-compose.

Official docker-compose setup: Agentfield provides a production-ready docker-compose.yml in the GitHub repository with PostgreSQL + control plane configuration.

Essential Docker Environment Variables

These variables are critical for Docker deployments. See Environment Variables Reference for the complete list.

VariableRequiredExampleNotes
AGENTFIELD_SERVERYeshttp://agentfield-server:8080Control plane URL (use service name in docker-compose)
AGENT_CALLBACK_URLCriticalhttp://agent-1:8000Where control plane reaches your agent. Must be network-accessible URL
PORTNo8000Agent server port (default: 8000)
AGENTFIELD_DATABASE_URLControl Planepostgres://user:pass@postgres:5432/agentfieldPostgreSQL connection (control plane only)

Critical: Set AGENT_CALLBACK_URL correctly

Control plane must reach your agent. Don't use localhost or 127.0.0.1 in containers.

Common patterns:

  • Docker Compose: http://agent-service-name:8000
  • Host + Docker: http://host.docker.internal:8000
  • Production: https://agent-1.company.com

Test it: docker exec agentfield-server curl http://your-agent-url:8000/health

Quick Start

Single Agent Container

Deploy a single agent in a Docker container:

# Dockerfile
FROM python:3.11-slim

WORKDIR /app

# Install dependencies
COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy agent code
COPY . .

# Expose agent port
EXPOSE 8001

# Run agent
CMD ["python", "main.py"]
# main.py
import os
from agentfield import Agent

app = Agent(
    node_id="docker_agent",
    agentfield_server=os.getenv("AGENTFIELD_SERVER", "http://localhost:8080"),
    callback_url=os.getenv("AGENT_CALLBACK_URL")
)

@app.reasoner
async def process(text: str) -> str:
    return await app.ai(user=text)

if __name__ == "__main__":
    app.serve(
        port=int(os.getenv("AGENT_PORT", 8001)),
        host="0.0.0.0"
    )

Build and run:

# Build image
docker build -t my-agent .

# Run container
docker run -d \
  --name my-agent \
  -p 8001:8001 \
  -e AGENTFIELD_SERVER=http://host.docker.internal:8080 \
  -e AGENT_CALLBACK_URL=http://host.docker.internal:8001 \
  -e OPENAI_API_KEY=your-api-key \
  my-agent

Docker Compose Setup

Full Stack Deployment

Deploy both the control plane and agents using docker-compose.

Official setup: Get the production-ready docker-compose.yml from github.com/Agent-Field/agentfield/deployments/docker/docker-compose.yml

Simplified example:

# docker-compose.yml
version: '3.9'

services:
  # PostgreSQL Database (required)
  postgres:
    image: postgres:15-alpine
    container_name: agentfield-postgres
    environment:
      - POSTGRES_DB=agentfield
      - POSTGRES_USER=agentfield
      - POSTGRES_PASSWORD=change-in-production
    volumes:
      - pgdata:/var/lib/postgresql/data
    restart: unless-stopped
    networks:
      - agentfield-network

  # Agentfield Control Plane
  control-plane:
    image: agentfield/control-plane:latest
    container_name: agentfield-server
    ports:
      - "8080:8080"
    environment:
      - AGENTFIELD_POSTGRES_URL=postgres://agentfield:change-in-production@postgres:5432/agentfield
      - AGENTFIELD_HTTP_ADDR=0.0.0.0:8080
    depends_on:
      - postgres
    networks:
      - agentfield-network

  # Agent Node 1
  agent-1:
    build: ./agents/sentiment-analyzer
    container_name: sentiment-agent
    ports:
      - "8001:8001"
    environment:
      - AGENTFIELD_SERVER=http://agentfield-server:8080
      - AGENT_CALLBACK_URL=http://agent-1:8001
      - OPENAI_API_KEY=${OPENAI_API_KEY}
    depends_on:
      - agentfield-server
    networks:
      - agentfield-network

  # Agent Node 2
  agent-2:
    build: ./agents/data-processor
    container_name: processor-agent
    ports:
      - "8002:8002"
    environment:
      - AGENTFIELD_SERVER=http://agentfield-server:8080
      - AGENT_CALLBACK_URL=http://agent-2:8002
      - AGENT_PORT=8002
      - OPENAI_API_KEY=${OPENAI_API_KEY}
    depends_on:
      - agentfield-server
    networks:
      - agentfield-network

networks:
  agentfield-network:
    driver: bridge

volumes:
  pgdata:

networks:
  agentfield-network:
    driver: bridge

Start the stack:

```bash
# Create .env file with secrets
echo "OPENAI_API_KEY=your-api-key" > .env

# Start all services
docker-compose up -d

# View logs
docker-compose logs -f

# Stop all services
docker-compose down

Environment Variables

Required Variables

Prop

Type

Optional Variables

Prop

Type

Networking Scenarios

Scenario 1: Agent on Host, Control Plane in Docker

When the control plane runs in Docker but agents run on the host:

# Start control plane in Docker
docker run -d \
  --name agentfield-server \
  -p 8080:8080 \
  agentfield/control-plane:latest

# Run agent on host
export AGENTFIELD_SERVER=http://localhost:8080
export AGENT_CALLBACK_URL=http://host.docker.internal:8001
python main.py

Scenario 2: Both in Docker (Same Network)

When both control plane and agents run in Docker on the same network:

# docker-compose.yml
services:
  agentfield-server:
    image: agentfield/control-plane:latest
    networks:
      - agentfield-network

  my-agent:
    build: .
    environment:
      - AGENTFIELD_SERVER=http://agentfield-server:8080
      - AGENT_CALLBACK_URL=http://my-agent:8001
    networks:
      - agentfield-network

networks:
  agentfield-network:

Use service names for internal communication.

Scenario 3: Production with External URLs

For production deployments with load balancers:

services:
  my-agent:
    build: .
    environment:
      - AGENTFIELD_SERVER=https://agentfield.company.com
      - AGENT_CALLBACK_URL=https://agent-1.company.com
      - OPENAI_API_KEY=${OPENAI_API_KEY}

Health Checks

Add health checks to your agents:

# main.py
from agentfield import Agent
from fastapi import Response

app = Agent(node_id="my_agent")

@app.get("/health")
async def health_check():
    """Health check endpoint for Docker."""
    return {"status": "healthy", "node_id": app.node_id}

@app.get("/ready")
async def readiness_check():
    """Readiness check - verify connection to control plane."""
    try:
        # Verify control plane connectivity
        response = await app.http_client.get(
            f"{app.agentfield_server}/health"
        )
        if response.status_code == 200:
            return {"status": "ready"}
    except Exception as e:
        return Response(
            content={"status": "not_ready", "error": str(e)},
            status_code=503
        )

Docker health check configuration:

HEALTHCHECK --interval=30s --timeout=10s --start-period=5s --retries=3 \
  CMD curl -f http://localhost:8001/health || exit 1

Production Best Practices

For production hardening (non-root users, resource limits, secrets management, logging), see the Production Hardening Guide.

Quick checklist:

  • Use non-root user in Dockerfile
  • Set resource limits (CPU, memory)
  • Configure log rotation
  • Use Docker secrets for API keys
  • Add health checks to containers

Troubleshooting

Agent Cannot Connect to Control Plane

Symptom: Agent fails to register with control plane

Solutions:

  1. Verify network connectivity:
docker exec my-agent curl http://agentfield-server:8080/health
  1. Check environment variables:
docker exec my-agent env | grep AGENTFIELD
  1. Verify DNS resolution:
docker exec my-agent nslookup agentfield-server

Control Plane Cannot Reach Agent

Symptom: Executions fail with connection errors

Solutions:

  1. Verify AGENT_CALLBACK_URL is set correctly:
# For host-based agents with Docker control plane
export AGENT_CALLBACK_URL=http://host.docker.internal:8001

# For Docker-based agents
export AGENT_CALLBACK_URL=http://agent-service-name:8001
  1. Test callback URL from control plane:
docker exec agentfield-server curl http://host.docker.internal:8001/health
  1. Check firewall rules and port bindings

Port Already in Use

Symptom: Address already in use error

Solutions:

  1. Find and stop conflicting container:
docker ps | grep 8001
docker stop <container-id>
  1. Use different port:
docker run -p 8002:8001 my-agent

Scaling

# Scale control plane horizontally
docker-compose up --scale control-plane=3

# Scale specific agent independently
docker-compose up --scale support-agent=5