Skip to main content

Docker Image

Motia provides official Docker images built with security and performance in mind:
  • Base: Distroless (gcr.io/distroless/cc-debian12:nonroot)
  • Runtime: Non-root user execution
  • Size: Minimal attack surface (no shell, no package manager)
  • Registry: iiidev/iii:latest

Pull the Image

docker pull iiidev/iii:latest

Run Container

Basic run command:
docker run -p 3111:3111 -p 49134:49134 \
  -v ./config.yaml:/app/config.yaml:ro \
  iiidev/iii:latest

Exposed Ports

The Dockerfile exposes four ports:
EXPOSE 49134 3111 3112 9464
PortServiceDescription
49134WebSocketWorker connections
3111REST APIHTTP endpoints for registered functions
3112Stream APIReal-time state synchronization
9464MetricsPrometheus metrics endpoint

Docker Compose

Development Stack

The docker-compose.yml includes the full development stack with Redis and RabbitMQ:
services:
  iii:
    image: iiidev/iii:latest
    # Or build from source instead:
    # build: .
    ports:
      - "49134:49134"  # WebSocket (worker connections)
      - "3111:3111"    # REST API
      - "3112:3112"    # Stream API
      - "9464:9464"    # Prometheus metrics
    volumes:
      - ./config.yaml:/app/config.yaml:ro
    environment:
      - RUST_LOG=info
    depends_on:
      redis:
        condition: service_healthy
      rabbitmq:
        condition: service_healthy
    restart: unless-stopped

  redis:
    image: redis:7-alpine
    ports:
      - "6379:6379"
    volumes:
      - redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    restart: unless-stopped

  rabbitmq:
    image: rabbitmq:3-management-alpine
    ports:
      - "5672:5672"    # AMQP
      - "15672:15672"  # Management UI
    environment:
      # LOCAL DEV ONLY - override in production via .env or secrets
      - RABBITMQ_DEFAULT_USER=${RABBITMQ_USER:-guest}
      - RABBITMQ_DEFAULT_PASS=${RABBITMQ_PASS:-guest}
    volumes:
      - rabbitmq_data:/var/lib/rabbitmq
    healthcheck:
      test: ["CMD", "rabbitmq-diagnostics", "check_running"]
      interval: 10s
      timeout: 5s
      retries: 5
    restart: unless-stopped

volumes:
  redis_data:
  rabbitmq_data:

Start the Stack

docker compose up -d

View Logs

docker compose logs -f iii

Stop the Stack

docker compose down

Production Stack with Caddy

The docker-compose.prod.yml includes Caddy as a reverse proxy with automatic HTTPS:
services:
  caddy:
    image: caddy:2-alpine
    ports:
      - "80:80"
      - "443:443"
      - "443:443/udp"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy_data:/data
      - caddy_config:/config
    depends_on:
      iii:
        condition: service_healthy
    restart: unless-stopped

  iii:
    image: iiidev/iii:latest
    volumes:
      - ./config.prod.yaml:/app/config.yaml:ro
    healthcheck:
      test: ["CMD-SHELL", "nc -z 127.0.0.1 3111 || exit 1"]
      interval: 10s
      timeout: 5s
      retries: 5
      start_period: 10s
    restart: unless-stopped

volumes:
  caddy_data:
  caddy_config:

Caddyfile Configuration

Create a Caddyfile for reverse proxy setup:
your-domain.com {
	handle /api/* {
		reverse_proxy iii:3111
	}

	handle /streams/* {
		reverse_proxy iii:3112
	}

	handle /ws {
		reverse_proxy iii:49134
	}

	handle {
		reverse_proxy iii:3111
	}
}

Start Production Stack

docker compose -f docker-compose.prod.yml up -d

Building from Source

Production Image

The Dockerfile uses multi-stage builds with cargo-chef for optimized layer caching:
FROM rust:slim-bookworm AS chef
WORKDIR /build

RUN apt-get update && apt-get install -y --no-install-recommends \
    pkg-config libssl-dev \
    && rm -rf /var/lib/apt/lists/* \
    && cargo install cargo-chef

FROM chef AS planner
COPY . .
RUN cargo chef prepare --recipe-path recipe.json

FROM chef AS builder
COPY --from=planner /build/recipe.json recipe.json
COPY Cargo.toml Cargo.lock ./
COPY function-macros ./function-macros
RUN cargo chef cook --release --recipe-path recipe.json
COPY . .
RUN cargo build --release && strip target/release/iii

FROM gcr.io/distroless/cc-debian12:nonroot

COPY --from=builder /build/target/release/iii /app/iii

EXPOSE 49134 3111 3112 9464
ENTRYPOINT ["/app/iii"]
CMD ["--config", "/app/config.yaml"]

LABEL org.opencontainers.image.title="III Engine" \
      org.opencontainers.image.vendor="iiidev" \
      org.opencontainers.image.source="https://github.com/iii-hq/iii"

Build Locally

docker build -t iii:local .

Build Debug Image

For debugging with shell access:
docker build -f Dockerfile.debug -t iii:debug .

Volume Mounts

Configuration

Mount your config file as read-only:
-v ./config.yaml:/app/config.yaml:ro

Data Persistence

For file-based KV stores, mount a data directory:
-v ./data:/app/data

Environment Variables

Logging

-e RUST_LOG=info          # Log level: debug, info, warn, error

Configuration Expansion

The engine supports environment variable expansion in config files:
modules:
  - class: modules::stream::StreamModule
    config:
      port: ${STREAM_PORT:3112}
      adapter:
        class: modules::stream::adapters::RedisAdapter
        config:
          redis_url: ${REDIS_URL:redis://localhost:6379}
Set via Docker:
-e STREAM_PORT=3112 \
-e REDIS_URL=redis://redis:6379

Security Hardening

For production deployments, use security options:
docker run --read-only --tmpfs /tmp \
  --cap-drop=ALL --cap-add=NET_BIND_SERVICE \
  --security-opt=no-new-privileges:true \
  -v ./config.yaml:/app/config.yaml:ro \
  -p 3111:3111 -p 49134:49134 -p 3112:3112 -p 9464:9464 \
  iiidev/iii:latest
See Production Deployment for comprehensive security practices.

Next Steps

Production Setup

Security hardening and scaling strategies

Configuration

Configure modules and environment variables

Build docs developers (and LLMs) love