Skip to content

Deploy Aegis as a REST API via Docker

Run Aegis as a standalone governance server in Docker. Any service -- Python, Go, TypeScript, Java, Rust -- can evaluate actions over HTTP. No SDK integration required.

This guide walks through building the image, using the API endpoints, customizing policies, and hardening for production.

What you will build: A containerized REST API that evaluates agent actions against YAML policies, with health checks, hot-reload, and an audit trail.

Time: 10 minutes.


Prerequisites

  • Docker installed and running
  • The Aegis repository cloned (or just the examples/docker/ directory)
git clone https://github.com/Acacian/aegis.git
cd aegis

Building and Running the Docker Image

Quick Start

docker build -t aegis-server examples/docker/
docker run -p 8000:8000 aegis-server

The server starts on http://localhost:8000 with the default policy from examples/docker/policy.yaml.

Verify it works:

curl http://localhost:8000/health
# => {"status": "ok", "version": "..."}

Mount Your Own Policy

The default policy is baked into the image. Override it by mounting your own policy.yaml:

docker run -p 8000:8000 \
  -v $(pwd)/policy.yaml:/app/policy.yaml \
  aegis-server

Docker Compose

For repeatable deployments, use Compose:

# docker-compose.yaml
services:
  aegis:
    build: examples/docker/
    ports:
      - "8000:8000"
    volumes:
      - ./policy.yaml:/app/policy.yaml
      - aegis-data:/app/data
    healthcheck:
      test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health"]
      interval: 30s
      timeout: 5s
      retries: 3

volumes:
  aegis-data:
docker compose up -d
docker compose logs -f aegis

What the Dockerfile Does

The image (examples/docker/Dockerfile) is minimal by design:

  1. Starts from python:3.12-slim (~120 MB).
  2. Installs agent-aegis[server] (includes Starlette + Uvicorn).
  3. Copies the default policy.yaml into /app/.
  4. Exposes port 8000 with a built-in health check.
  5. Runs aegis serve /app/policy.yaml --host 0.0.0.0 --port 8000.

No application code to maintain -- the CLI aegis serve command handles everything.


API Endpoints

The server exposes six endpoints. All request/response bodies are JSON.

GET /health

Health check. Use for Docker HEALTHCHECK, load balancers, and uptime monitors.

curl http://localhost:8000/health
{"status": "ok", "version": "0.1.3"}

POST /api/v1/evaluate

Evaluate an action against the policy without executing it. This is a dry-run -- no side effects, no audit entry.

curl -X POST http://localhost:8000/api/v1/evaluate \
  -H "Content-Type: application/json" \
  -d '{"action_type": "read", "target": "crm"}'
{
  "action_type": "read",
  "target": "crm",
  "risk_level": "LOW",
  "approval": "auto",
  "is_allowed": true,
  "matched_rule": "read_auto"
}

Batch evaluation -- send multiple actions in one request:

curl -X POST http://localhost:8000/api/v1/evaluate \
  -H "Content-Type: application/json" \
  -d '{"actions": [
    {"action_type": "read", "target": "crm"},
    {"action_type": "write", "target": "database"},
    {"action_type": "delete", "target": "storage"}
  ]}'

Returns an array of decision objects.

POST /api/v1/execute

Execute an action through the full governance pipeline: policy check, approval gate, execution, and audit logging.

curl -X POST http://localhost:8000/api/v1/execute \
  -H "Content-Type: application/json" \
  -d '{"action_type": "read", "target": "crm"}'
{
  "action_type": "read",
  "target": "crm",
  "status": "success",
  "data": {"executed": true},
  "error": null
}

Default No-Op Executor

The built-in server uses a no-op executor -- /execute runs the full governance pipeline but does not perform real side effects. To connect real actions, use the programmatic API with a custom executor.

GET /api/v1/audit

Query the audit log. Supports filtering by multiple parameters.

# All entries
curl http://localhost:8000/api/v1/audit

# Filter by action type
curl "http://localhost:8000/api/v1/audit?action_type=delete"

# Filter by risk level
curl "http://localhost:8000/api/v1/audit?risk_level=CRITICAL"

# Combine filters with a limit
curl "http://localhost:8000/api/v1/audit?result_status=blocked&limit=20"

Supported query parameters:

Parameter Example Description
session_id a3f1b2c4 Filter by session
action_type delete Filter by action type
risk_level HIGH Filter by risk level
result_status blocked Filter by execution result
limit 50 Max entries to return

GET /api/v1/policy

Inspect the currently active policy rules.

curl http://localhost:8000/api/v1/policy
{
  "default_risk_level": "MEDIUM",
  "default_approval": "approve",
  "rules": [
    {
      "name": "read_auto",
      "match_type": "read*",
      "match_target": null,
      "risk_level": "LOW",
      "approval": "auto",
      "conditions": null
    }
  ]
}

PUT /api/v1/policy

Hot-reload the policy without restarting the server. Send either a YAML string or a policy dict.

# From a YAML string
curl -X PUT http://localhost:8000/api/v1/policy \
  -H "Content-Type: application/json" \
  -d '{"yaml": "version: \"1\"\nrules:\n  - name: block_all\n    match: {type: \"*\"}\n    approval: block"}'
# From a policy dict
curl -X PUT http://localhost:8000/api/v1/policy \
  -H "Content-Type: application/json" \
  -d '{
    "defaults": {"risk_level": "high", "approval": "block"},
    "rules": [
      {"name": "allow_reads", "match": {"type": "read*"}, "approval": "auto"}
    ]
  }'
{"status": "updated", "rule_count": 1}

Custom Policy Configuration

Default Policy

The image ships with a default policy (examples/docker/policy.yaml) that covers basic CRUD governance:

version: "1"
defaults:
  risk_level: medium
  approval: approve

rules:
  - name: read_auto
    match: { type: "read*" }
    risk_level: low
    approval: auto

  - name: write_approve
    match: { type: "write*" }
    risk_level: medium
    approval: approve

  - name: delete_block
    match: { type: "delete*" }
    risk_level: critical
    approval: block

Writing Your Own Policy

Create a policy.yaml tailored to your use case and mount it into the container:

# policy.yaml -- E-commerce agent governance
version: "1"
defaults:
  risk_level: high
  approval: block          # Fail closed: unknown actions are blocked

rules:
  - name: product_search
    match: { type: "search_*", target: "catalog" }
    risk_level: low
    approval: auto

  - name: cart_operations
    match: { type: "cart_*", target: "session" }
    risk_level: low
    approval: auto

  - name: place_order
    match: { type: "place_order" }
    conditions:
      param_lte: { total: 500 }
    risk_level: medium
    approval: approve

  - name: block_large_orders
    match: { type: "place_order" }
    conditions:
      param_gt: { total: 500 }
    risk_level: critical
    approval: block

  - name: refund
    match: { type: "refund" }
    risk_level: high
    approval: approve
docker run -p 8000:8000 \
  -v $(pwd)/policy.yaml:/app/policy.yaml \
  aegis-server

Policy Validation

Validate your policy before deploying:

pip install agent-aegis
aegis validate policy.yaml

Or validate via the API after deployment:

# Inspect active rules
curl http://localhost:8000/api/v1/policy | python -m json.tool

# Test a specific action
curl -X POST http://localhost:8000/api/v1/evaluate \
  -H "Content-Type: application/json" \
  -d '{"action_type": "place_order", "target": "checkout", "params": {"total": 1000}}'

Programmatic Usage

For tighter integration, create the ASGI app directly in Python:

from aegis.server import create_app

app = create_app(
    policy_path="policy.yaml",
    audit_db_path="/data/audit.db",  # Persistent audit storage
)

# Run with uvicorn
import uvicorn
uvicorn.run(app, host="0.0.0.0", port=8000)

To connect real tool execution (not just policy evaluation):

from aegis.server import create_app

app = create_app(
    policy_path="policy.yaml",
    executor=MyCustomExecutor(),   # Your executor handles real actions
    audit_db_path="/data/audit.db",
)

Production Deployment Tips

Run as Non-Root

The default Dockerfile runs as root. For production, create a dedicated user:

FROM python:3.12-slim

RUN useradd -m aegis
WORKDIR /home/aegis

RUN pip install --no-cache-dir 'agent-aegis[server]'

COPY policy.yaml .

USER aegis
EXPOSE 8000

HEALTHCHECK --interval=30s --timeout=5s \
  CMD python -c "import urllib.request; urllib.request.urlopen('http://localhost:8000/api/v1/health')" || exit 1

ENTRYPOINT ["aegis", "serve", "policy.yaml", "--host", "0.0.0.0", "--port", "8000"]

Pin the Version

Avoid surprises by pinning the exact Aegis version:

RUN pip install --no-cache-dir 'agent-aegis[server]==0.1.3'

Harden the Container

Apply defense-in-depth settings in your Compose file:

services:
  aegis:
    build: .
    ports:
      - "8000:8000"
    volumes:
      - ./policy.yaml:/app/policy.yaml:ro
      - aegis-data:/app/data
    read_only: true
    tmpfs: /tmp
    security_opt:
      - no-new-privileges
    cap_drop:
      - ALL
    environment:
      - AEGIS_LOG_LEVEL=INFO

Key settings:

Setting Purpose
read_only: true Container filesystem is immutable
tmpfs: /tmp Writable temp space without persisting to disk
cap_drop: ALL Drop all Linux capabilities
no-new-privileges Prevent privilege escalation
:ro on policy mount Policy file is read-only inside the container

Deploy Behind a Reverse Proxy

The Aegis server has no built-in authentication or TLS. In production, place it behind a reverse proxy:

# nginx.conf
upstream aegis {
    server aegis:8000;
}

server {
    listen 443 ssl;
    server_name aegis.yourcompany.com;

    ssl_certificate     /etc/ssl/certs/aegis.crt;
    ssl_certificate_key /etc/ssl/private/aegis.key;

    location / {
        proxy_pass http://aegis;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

Auto-Approval Default

The REST server uses AutoApprovalHandler by default -- all approval-required actions are auto-approved. For production, deploy behind an authenticating reverse proxy and consider implementing a custom approval handler.

Persistent Audit Storage

By default, the audit log lives in an in-memory SQLite database and is lost when the container stops. Mount a volume for persistence:

docker run -p 8000:8000 \
  -v $(pwd)/policy.yaml:/app/policy.yaml \
  -v aegis-data:/app/data \
  -e AEGIS_AUDIT_DB=/app/data/audit.db \
  aegis-server

Environment Variables

Variable Default Description
AEGIS_POLICY_PATH policy.yaml Path to the YAML policy file
AEGIS_LOG_LEVEL WARNING Log level (DEBUG, INFO, WARNING, ERROR)
AEGIS_AUDIT_DB aegis_audit.db Path to the SQLite audit database

Health Check Integration

The Dockerfile includes a built-in health check. For orchestrators:

Docker Compose:

healthcheck:
  test: ["CMD", "curl", "-f", "http://localhost:8000/api/v1/health"]
  interval: 30s
  timeout: 5s
  retries: 3

Kubernetes:

livenessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 5
  periodSeconds: 30
readinessProbe:
  httpGet:
    path: /health
    port: 8000
  initialDelaySeconds: 3
  periodSeconds: 10

End-to-End Example

A complete workflow from build to test:

# 1. Build the image
docker build -t aegis-server examples/docker/

# 2. Start the server with a custom policy
docker run -d --name aegis -p 8000:8000 \
  -v $(pwd)/policy.yaml:/app/policy.yaml \
  aegis-server

# 3. Verify health
curl http://localhost:8000/health

# 4. Evaluate a safe action
curl -X POST http://localhost:8000/api/v1/evaluate \
  -H "Content-Type: application/json" \
  -d '{"action_type": "read", "target": "crm"}'
# => {"risk_level": "LOW", "approval": "auto", "is_allowed": true, ...}

# 5. Evaluate a blocked action
curl -X POST http://localhost:8000/api/v1/evaluate \
  -H "Content-Type: application/json" \
  -d '{"action_type": "delete", "target": "crm"}'
# => {"risk_level": "CRITICAL", "approval": "block", "is_allowed": false, ...}

# 6. Hot-reload a stricter policy
curl -X PUT http://localhost:8000/api/v1/policy \
  -H "Content-Type: application/json" \
  -d '{"defaults": {"risk_level": "high", "approval": "block"}, "rules": []}'
# => {"status": "updated", "rule_count": 0}

# 7. Now everything is blocked
curl -X POST http://localhost:8000/api/v1/evaluate \
  -H "Content-Type: application/json" \
  -d '{"action_type": "read", "target": "crm"}'
# => {"risk_level": "HIGH", "approval": "block", "is_allowed": false, ...}

# 8. Clean up
docker stop aegis && docker rm aegis

Next Steps