Skip to main content
iii is designed with security in mind, but proper configuration is essential for production deployments. This guide covers security best practices for running iii in production.

Production Docker Deployment

Hardened Container

Use security-hardened Docker configuration:
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
Security features:
  • --read-only: Immutable root filesystem
  • --tmpfs /tmp: Writable temporary storage
  • --cap-drop=ALL: Drop all Linux capabilities
  • --cap-add=NET_BIND_SERVICE: Allow binding to privileged ports only
  • --security-opt=no-new-privileges: Prevent privilege escalation
  • :ro: Mount config as read-only

Distroless Base Image

iii uses Google’s distroless base image for minimal attack surface:
FROM gcr.io/distroless/cc-debian12:nonroot

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

EXPOSE 49134 3111 3112 9464
ENTRYPOINTYP [/app/iii"]
CMD ["--config", "/app/config.yaml"]
Benefits:
  • No shell or package manager
  • Minimal dependencies
  • Non-root user by default
  • Smaller attack surface

TLS with Caddy

Reverse Proxy Setup

Use Caddy for automatic TLS with Let’s Encrypt: Caddyfile:
your-domain.com {
    # HTTP API
    handle /api/* {
        reverse_proxy iii:3111
    }

    # Stream API
    handle /streams/* {
        reverse_proxy iii:3112
    }

    # WebSocket (worker connections)
    handle /ws {
        reverse_proxy iii:49134
    }

    # Default handler
    handle {
        reverse_proxy iii:3111
    }
}
Docker Compose:
services:
  caddy:
    image: caddy:latest
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:ro
      - caddy-data:/data
      - caddy-config:/config
    depends_on:
      - iii
  
  iii:
    image: iiidev/iii:latest
    read_only: true
    tmpfs:
      - /tmp
    cap_drop:
      - ALL
    cap_add:
      - NET_BIND_SERVICE
    security_opt:
      - no-new-privileges:true
    volumes:
      - ./config.yaml:/app/config.yaml:ro
    # Don't expose ports publicly - only to Caddy
    expose:
      - "3111"
      - "49134"
      - "3112"
      - "9464"

volumes:
  caddy-data:
  caddy-config:

WebSocket over TLS

Connect workers using wss:// (secure WebSocket):
import { init } from 'iii-sdk';

const iii = init('wss://your-domain.com/ws');

Custom TLS Certificates

For custom certificates, mount them in Caddy:
your-domain.com {
    tls /etc/caddy/certs/cert.pem /etc/caddy/certs/key.pem
    
    handle /ws {
        reverse_proxy iii:49134
    }
}

Authentication

HTTP Function Authentication

iii supports multiple authentication methods for external HTTP functions:

Bearer Token

{
  "type": "registerfunction",
  "id": "external.api",
  "invocation": {
    "url": "https://api.example.com/endpoint",
    "auth": {
      "type": "bearer",
      "token_key": "API_TOKEN"  // Environment variable name
    }
  }
}
Set the token in environment:
export API_TOKEN="your-secret-token"

API Key

{
  "type": "registerfunction",
  "id": "external.api",
  "invocation": {
    "url": "https://api.example.com/endpoint",
    "auth": {
      "type": "api_key",
      "header": "X-API-Key",
      "value_key": "EXTERNAL_API_KEY"
    }
  }
}

HMAC Signature

{
  "type": "registerfunction",
  "id": "external.api",
  "invocation": {
    "url": "https://api.example.com/endpoint",
    "auth": {
      "type": "hmac",
      "secret_key": "HMAC_SECRET"
    }
  }
}
Always use environment variables for secrets. Never hardcode credentials in configuration files or code.

API Authentication

For the HTTP API, implement authentication via middleware or reverse proxy: Caddy with Basic Auth:
your-domain.com {
    # Require auth for API
    handle /api/* {
        basicauth {
            admin $2a$14$Zkx19XLiW6VYouLHR5NmfOFU0z2GTNmpkT/5qqR7hx16A0vyNsN8q
        }
        reverse_proxy iii:3111
    }
    
    # No auth for WebSocket (implement custom auth in worker)
    handle /ws {
        reverse_proxy iii:49134
    }
}
Caddy with JWT:
your-domain.com {
    handle /api/* {
        jwt {
            secret your-secret-key
        }
        reverse_proxy iii:3111
    }
}

Network Security

Port Exposure

Public-facing ports:
  • 443 (HTTPS) - Caddy/reverse proxy only
  • 80 (HTTP) - Redirect to HTTPS
Internal ports (not exposed):
  • 3111 - HTTP API (behind proxy)
  • 49134 - WebSocket (behind proxy)
  • 3112 - Stream API (behind proxy)
  • 9464 - Prometheus metrics (internal only)
Docker configuration:
services:
  iii:
    # Use 'expose' instead of 'ports' for internal-only access
    expose:
      - "3111"
      - "49134"
      - "3112"
    
    # Only expose metrics to internal network
    ports:
      - "127.0.0.1:9464:9464"  # Bind to localhost only

Firewall Rules

Configure firewall to restrict access:
# Allow HTTPS only
sudo ufw allow 443/tcp

# Allow HTTP for Let's Encrypt challenges
sudo ufw allow 80/tcp

# Deny direct access to iii ports
sudo ufw deny 3111/tcp
sudo ufw deny 49134/tcp
sudo ufw deny 3112/tcp
sudo ufw deny 9464/tcp

Network Isolation

Use Docker networks for service isolation:
services:
  caddy:
    networks:
      - public
      - internal
  
  iii:
    networks:
      - internal
  
  redis:
    networks:
      - internal

networks:
  public:
    driver: bridge
  internal:
    driver: bridge
    internal: true  # No external access

Secrets Management

Environment Variables

Use Docker secrets or external secret managers: Docker Swarm Secrets:
services:
  iii:
    secrets:
      - redis_password
      - api_token
    environment:
      REDIS_PASSWORD_FILE: /run/secrets/redis_password
      API_TOKEN_FILE: /run/secrets/api_token

secrets:
  redis_password:
    external: true
  api_token:
    external: true
Kubernetes Secrets:
apiVersion: v1
kind: Secret
metadata:
  name: iii-secrets
type: Opaque
data:
  redis-password: <base64-encoded>
  api-token: <base64-encoded>
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: iii
spec:
  template:
    spec:
      containers:
      - name: iii
        env:
        - name: REDIS_PASSWORD
          valueFrom:
            secretKeyRef:
              name: iii-secrets
              key: redis-password

Vault Integration

Integrate with HashiCorp Vault:
# Fetch secrets from Vault
export REDIS_PASSWORD=$(vault kv get -field=password secret/iii/redis)
export API_TOKEN=$(vault kv get -field=token secret/iii/api)

docker run \
  -e REDIS_PASSWORD \
  -e API_TOKEN \
  iiidev/iii:latest

Configuration Security

Environment Variable Expansion

iii supports environment variable expansion in config files:
modules:
  - class: modules::queue::QueueModule
    config:
      adapter:
        class: modules::queue::adapters::RedisQueueAdapter
        config:
          # Use default if env var not set
          url: ${REDIS_URL:redis://localhost:6379}
          
          # Required env var (no default)
          password: ${REDIS_PASSWORD}

Read-Only Configuration

Mount configuration as read-only:
docker run -v ./config.yaml:/app/config.yaml:ro iiidev/iii:latest

Sensitive Data Filtering

iii automatically redacts sensitive fields in logs:
  • Passwords
  • API keys
  • Tokens
  • Connection strings

Monitoring Security

Secure Metrics Endpoint

Restrict Prometheus metrics access: Caddy with IP allowlist:
:9464 {
    @allowed {
        remote_ip 10.0.0.0/8 172.16.0.0/12 192.168.0.0/16
    }
    
    handle @allowed {
        reverse_proxy iii:9464
    }
    
    handle {
        respond "Forbidden" 403
    }
}
Prometheus with basic auth:
scrape_configs:
  - job_name: 'iii'
    static_configs:
      - targets: ['iii:9464']
    basic_auth:
      username: prometheus
      password: ${PROMETHEUS_PASSWORD}

Audit Logging

Enable audit logging for security events:
modules:
  - class: modules::observability::LoggingModule
    config:
      level: info
      format: json
      audit:
        enabled: true
        events:
          - function_registration
          - trigger_registration
          - authentication_failure

Resource Limits

Docker Resource Constraints

services:
  iii:
    deploy:
      resources:
        limits:
          cpus: '2'
          memory: 2G
        reservations:
          cpus: '1'
          memory: 1G

Rate Limiting

Implement rate limiting at the reverse proxy:
your-domain.com {
    rate_limit {
        zone api_zone {
            key {remote_host}
            events 100
            window 1m
        }
    }
    
    handle /api/* {
        rate_limit api_zone
        reverse_proxy iii:3111
    }
}

Incident Response

Security Monitoring

Monitor for security events:
alerts:
  - name: authentication_failures
    metric: auth.failures
    threshold: 10
    operator: ">"
    window_seconds: 60
    action:
      type: webhook
      url: https://security-team.slack.com/webhook
  
  - name: unusual_function_registration
    metric: functions.registered
    threshold: 50
    operator: ">"
    window_seconds: 300
    action:
      type: function
      path: security.alert

Log Retention

Configure appropriate log retention:
logs_retention_seconds: 604800  # 7 days
logs_max_count: 100000
Export logs to external SIEM:
logs_exporter: otlp
endpoint: https://siem.example.com:4317

Compliance

Data Encryption

  • In transit: TLS 1.3 for all network communication
  • At rest: Use encrypted volumes for persistent data
volumes:
  redis-data:
    driver: local
    driver_opts:
      type: none
      o: bind,encryption=aes-256-gcm
      device: /mnt/encrypted/redis

Data Retention

Configure retention policies:
metrics_retention_seconds: 2592000  # 30 days
logs_retention_seconds: 604800      # 7 days
traces_retention_seconds: 604800    # 7 days

Access Control

Implement least-privilege access:
  • Separate service accounts for each component
  • Use RBAC for Kubernetes deployments
  • Implement network policies
  • Regular access reviews

Security Checklist

  • Use TLS for all external communication
  • Run containers as non-root user
  • Use read-only root filesystem
  • Drop all unnecessary capabilities
  • Implement authentication on all APIs
  • Use environment variables for secrets
  • Enable security audit logging
  • Configure resource limits
  • Implement rate limiting
  • Restrict network access with firewall
  • Use private Docker networks
  • Mount configs as read-only
  • Enable security monitoring alerts
  • Regular security updates
  • Backup encryption keys
  • Document incident response plan

References

Build docs developers (and LLMs) love