Docker Deployment

This guide covers deploying ai_nn_controller using Docker.

Docker Compose Setup

The framework uses Docker Compose to orchestrate all services.

Starting Services

# Start all services in background
docker compose up -d

# View logs
docker compose logs -f

# Stop all services
docker compose down

Other Compose Files

The repository ships three Compose files for different scenarios:

Compose Files

File

Purpose

docker-compose.yml

Primary workflow — 6 simulated optical nodes + aic_server running control_application_v2_example. Use this for local development and the quickstart.

docker-compose.srsran.yml

srsRAN integration — swaps the 6 optical nodes for a single srsran_node (polls InfluxDB) and srsran_reader (a read-only control app). Requires an external docker_metrics network provided by a separately-running srsRAN + InfluxDB stack. See srsRAN Integration Example.

deploy/compose/docker-compose.arch.yml

An archived/reference variant of the root docker-compose.yml, kept for reference. Not part of the primary workflow.

Services Overview

Docker Services

Service

Container

Port

Description

redis

redis

6379

State storage

aic_register

aic_register

5558

Registration service

node_msg_broker

node_msg_broker

5554-5557

Message routing

aic_server

aic_server

8000

FastAPI + MCP

amp1_node

amp1_node

Simulated amplifier (node_id 3)

roadm1_node

roadm1_node

Simulated ROADM (node_id 4)

amp2_node

amp2_node

Simulated amplifier (node_id 5)

amp3_node

amp3_node

Simulated amplifier (node_id 6)

roadm2_node

roadm2_node

Simulated ROADM (node_id 7)

roadm3_node

roadm3_node

Simulated ROADM with commands (node_id 8)

fastapi_client

fastapi_client

CLI client container

docker-compose.yml Example

This is a trimmed version of the actual root docker-compose.yml (one dummy node shown instead of all six). Note that the aic_network network is created here with driver: bridge — a standalone app compose file (see Developing Control Applications) instead treats it as external: true and joins it.

services:
  redis:
    container_name: redis
    image: redis:7-alpine
    ports:
      - "6379:6379"
    networks:
      - aic_network
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5

  aic_register:
    container_name: aic_register
    build:
      context: ./controller_components/register/
      dockerfile: Dockerfile
    volumes:
      - ./controller_components/register/:/register
      - ./control_applications/:/control_applications
      - ./:/workspace
    command: python3 register.py
    depends_on:
      redis:
        condition: service_healthy
    networks:
      - aic_network
    ports:
      - "5558:5558"
    environment:
      - PYTHONUNBUFFERED=1

  node_msg_broker:
    container_name: node_msg_broker
    build:
      context: ./controller_components/node_msg_broker/
      dockerfile: Dockerfile
    depends_on:
      - aic_register
    networks:
      - aic_network
    ports:
      - "5554:5554"
      - "5555:5555"
      - "5557:5557"
    environment:
      - PYTHONUNBUFFERED=1

  # ... amp1_node / roadm1_node / amp2_node / amp3_node / roadm2_node /
  # roadm3_node all follow the same pattern:
  amp1_node:
    container_name: amp1_node
    build:
      context: ./
      dockerfile: network_nodes/dummy_nodes/amp1_node/Dockerfile
    volumes:
      - ./network_nodes/dummy_nodes/amp1_node/:/node
    depends_on:
      - aic_register
      - node_msg_broker
    networks:
      - aic_network
    command: >
      sh -c "sleep 5 && python3 node.py"
    environment:
      - PYTHONUNBUFFERED=1

  # Runs aic_app.py, which starts the controller with API via with_api=True.
  # Plugin installation order: framework -> plugins -> app. Plugins are
  # Python packages loaded inside this process, not separate containers.
  aic_server:
    container_name: aic_server
    build:
      context: ./
      dockerfile: control_applications/control_application_v2_example/Dockerfile
    volumes:
      - ./control_applications/control_application_v2_example/:/app
      - ./controller_components/ai_nn_controller/:/ai_nn_controller
      - ./plugins/console_plugin/:/console_plugin
    ports:
      - "8000:8000"
    networks:
      - aic_network
    environment:
      - PYTHONUNBUFFERED=1
    command: >
      sh -c "sleep 25
      && pip install --no-cache-dir /ai_nn_controller
      && pip install --no-cache-dir /console_plugin
      && pip install --no-cache-dir /app
      && python3 aic_app.py --verbose"

  fastapi_client:
    container_name: fastapi_client
    build:
      context: ./fastapi_client/
      dockerfile: Dockerfile
    volumes:
      - ./fastapi_client/:/app
    networks:
      - aic_network
    depends_on:
      - aic_server
    tty: true
    stdin_open: true
    environment:
      - PYTHONUNBUFFERED=1
    command: "/bin/bash"  # Interactive shell

networks:
  aic_network:
    driver: bridge

Creating Dockerfiles

Application Dockerfile

This is the actual Dockerfile used by control_applications/control_application_v2_example/Dockerfile. It shows the required install order: framework → plugins → app. Installing the app as a package (rather than just copying files) registers its ai_nn_controller.app_init entry point, which is what triggers register_specific_commands() automatically at startup.

FROM python:3.11-slim

WORKDIR /app

# Install system dependencies
RUN apt-get update && apt-get install -y net-tools procps curl

# Copy and install ai_nn_controller framework first
COPY controller_components/ai_nn_controller/ /ai_nn_controller/
RUN pip install /ai_nn_controller

# Copy and install plugins (must come after framework, before the app)
COPY plugins/console_plugin/ /console_plugin/
RUN pip install --no-cache-dir /console_plugin

# Copy requirements file and install app-specific dependencies
COPY control_applications/control_application_v2_example/requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

# Copy and install the control app as a package so its entry points are
# registered (ai_nn_controller.app_init -> bootstrap_application_bundle).
# This is what makes register_specific_commands() get called automatically.
COPY control_applications/control_application_v2_example/pyproject.toml /app_pkg/
COPY control_applications/control_application_v2_example/aic_app.py /app_pkg/
COPY control_applications/control_application_v2_example/commands.py /app_pkg/
RUN pip install --no-cache-dir /app_pkg

# Copy runtime files to WORKDIR (conf file + editable copies for docker
# compose volume-mount dev workflow)
COPY control_applications/control_application_v2_example/aic_app.py .
COPY control_applications/control_application_v2_example/aic_app.conf .
COPY control_applications/control_application_v2_example/commands.py .

# Set environment variables
ENV PYTHONUNBUFFERED=1

# Expose FastAPI port
EXPOSE 8000

# Run the AIC app directly - it starts the FastAPI server via with_api=True
CMD ["python3", "aic_app.py"]

See Developing Plugins for how the plugin install step works.

Node Dockerfile

FROM python:3.9-slim

WORKDIR /app

RUN pip install --no-cache-dir pyzmq requests

COPY my_node/ /app/

CMD ["python", "node.py"]

Adding Your App to Compose

Add your application to docker-compose.yml:

my_control_app:
  container_name: my_control_app
  build:
    context: ./
    dockerfile: control_applications/my_app/Dockerfile
  volumes:
    - ./control_applications/my_app/:/app
    - ./controller_components/ai_nn_controller/:/ai_nn_controller
  ports:
    - "8001:8000"  # Use different port if running multiple apps
  networks:
    - aic_network
  depends_on:
    - aic_register
    - node_msg_broker
  environment:
    - AIC_VERBOSE=true

Running Multiple Apps

To run multiple AIC applications:

# First app on port 8000
app_one:
  container_name: app_one
  build:
    context: ./
    dockerfile: control_applications/app_one/Dockerfile
  ports:
    - "8000:8000"
  networks:
    - aic_network
  depends_on:
    - aic_register
    - node_msg_broker

# Second app on port 8001
app_two:
  container_name: app_two
  build:
    context: ./
    dockerfile: control_applications/app_two/Dockerfile
  ports:
    - "8001:8000"
  networks:
    - aic_network
  depends_on:
    - aic_register
    - node_msg_broker

Development with Volumes

Use volumes for live code reloading during development:

aic_server:
  volumes:
    # Mount app code for live editing
    - ./control_applications/control_application_v2_example/:/app
    # Mount framework for development
    - ./controller_components/ai_nn_controller/:/ai_nn_controller
  environment:
    - AIC_VERBOSE=true

Useful Commands

# Build without cache
docker compose build --no-cache

# Start specific service
docker compose up -d aic_server

# View logs for specific service
docker compose logs -f aic_server

# Execute command in running container
docker compose exec aic_server python -c "print('hello')"

# Restart a service
docker compose restart aic_server

# Remove all containers and volumes
docker compose down -v

Health Checks

Add health checks to ensure services are ready:

aic_server:
  healthcheck:
    test: ["CMD", "curl", "-f", "http://localhost:8000/health"]
    interval: 10s
    timeout: 5s
    retries: 5
    start_period: 30s

Production Considerations

  1. Use specific image tags instead of :latest

  2. Set resource limits:

    deploy:
      resources:
        limits:
          cpus: '0.5'
          memory: 512M
    
  3. Use secrets for sensitive data

  4. Configure logging:

    logging:
      driver: "json-file"
      options:
        max-size: "10m"
        max-file: "3"
    
  5. Set restart policies:

    restart: unless-stopped
    

Troubleshooting

Container Won’t Start

# Check logs
docker compose logs aic_server

# Check if dependencies are running
docker compose ps

Network Issues

# Inspect network
docker network inspect ai_nn_controller_aic_network

# Test connectivity from container
docker compose exec aic_server ping aic_register

Port Conflicts

If ports are in use:

# Find process using port
lsof -i :8000

# Or change port in docker-compose.yml
ports:
  - "8080:8000"  # Use 8080 instead

Next Steps