Podman-based deployment for the AEGIS platform.
- Ubuntu 22.04/24.04 or Kali (Kali uses native Podman packages;
make setupdetects it) - Podman 4.0+ (rootless) --
make setupinstalls this on supported distros - GitHub PAT with
read:packages(orgh auth) for pulling images fromghcr.io/100monkeys-ai - Optional local LLM: LM Studio on
0.0.0.0:1234(infra comes up without a model)
git clone https://github.com/ar-fullsend/aegis-deploy.git
cd aegis-deploy
cp .env.example .env # set AEGIS_ROOT, GHCR_*, POSTGRES_PASSWORD
make setup # install Podman + dependencies
make deploy # development profile (includes IAM)
make bootstrap-keycloak # realm + clients (idempotent)
make generate-keys # SEAL RSA keys
make status && make validateOpenBao bootstrap runs automatically after the secrets pod. See docs/LOCAL-OPS.md for JWT, LM Studio, Grafana scrapes, and agent vs Temporal.
Select a profile with PROFILE=<name> make deploy. Default: development.
| Profile | Pods | Use Case |
|---|---|---|
minimal |
secrets, core | Local development with external DB |
development |
database, secrets, temporal, seal-gateway, iam, core, mcp, storage, observability | Full local dev (includes IAM + SeaweedFS + MCP) |
full |
database, secrets, storage, temporal, seal-gateway, core, mcp, observability | Complete platform storage path (no iam in profile) |
edge is optional and not in any profile: make redeploy POD=edge.
| Pod | Services | Ports |
|---|---|---|
| pod-core | aegis-runtime + metrics-proxy | 8088 (HTTP), 50051 (gRPC), 2049 (NFS), 9091 (loopback metrics), 9092 (Prometheus scrape) |
| pod-database | PostgreSQL 15, postgres-exporter | 5432, 9187 |
| pod-secrets | OpenBao | 8200 |
| pod-temporal | Temporal 1.23 (auto-setup), Temporal UI 2.21, aegis-temporal-worker | 7233 (gRPC), 8233 (UI) |
| pod-iam | Keycloak 24 | 8180 |
| pod-seal-gateway | aegis-seal-gateway | 8089 (HTTP), 50055 (gRPC) |
| pod-mcp | zaru-mcp-server (aegis-mcp-tools) | 8090 (MCP StreamableHTTP /mcp/v1) |
| pod-observability | Jaeger 1.55, Prometheus 2.51, Grafana 10.4, Loki 3.0, Promtail 3.0, otelcol-contrib 0.99 | 16686 (Jaeger UI), 4317/4318 (OTLP → otelcol), 9090 (Prometheus), 3300 (Grafana), 3100 (Loki) |
| pod-storage | SeaweedFS (master, volume, filer, WebDAV) | 9333 (master), 8080 (volume), 8888 (filer), 7333 (WebDAV) |
| host | FUSE daemon (FuseMountService gRPC) | 50053 — runs on the host as a systemd user service, not in a pod |
All pods join the aegis-network bridge network.
The AEGIS FUSE daemon is a host-side component -- it runs on the host as a systemd user service, not inside a container. It provides native POSIX filesystem access to workspace volumes via the FSAL security boundary.
Rootless Podman containers cannot mount FUSE filesystems internally, so the daemon runs on the host and exposes mountpoints that are bind-mounted into execution containers. This gives agents transparent read/write access to their workspace files.
- Connects to the orchestrator's gRPC endpoint for FSAL operations
- Mounts workspace volumes as FUSE filesystems on the host
- Execution containers access files through bind mounts from FUSE mountpoints
- All operations pass through the FSAL security boundary (tenant isolation, access policies)
The daemon is started automatically by make deploy and managed via systemd:
systemctl --user start aegis-fuse-daemon
systemctl --user stop aegis-fuse-daemon
systemctl --user status aegis-fuse-daemon
journalctl --user -u aegis-fuse-daemon -f # tail logsRequires the fuse3 package and fuse kernel module. The unit runs
%h/.local/bin/aegis (extracted from the runtime image into $REPO/bin/aegis).
It retries until orchestrator :50051 is up.
The pod-edge directory contains a Caddy reverse proxy.
Local (default in this repo): HTTP on port 80, auto_https off, no Cloudflare token required. Hostnames use DOMAIN_* from .env (defaults *.localhost).
| Subdomain Variable | Default | Backend (in-pod port) |
|---|---|---|
DOMAIN_API |
api.localhost |
aegis-core:8088 |
DOMAIN_SEAL |
seal.localhost |
aegis-seal-gateway:8089 |
DOMAIN_MCP |
mcp.localhost |
aegis-mcp:3000 (host maps 8090→3000) |
DOMAIN_TEMPORAL |
temporal.localhost |
aegis-temporal:8080 |
DOMAIN_GRAFANA |
grafana.localhost |
aegis-observability:3000 (host maps 3300→3000) |
DOMAIN_PROMETHEUS |
prometheus.localhost |
aegis-observability:9090 |
DOMAIN_JAEGER |
jaeger.localhost |
aegis-observability:16686 |
DOMAIN_SECRETS |
secrets.localhost |
aegis-secrets:8200 |
Production TLS: restore Cloudflare ACME (acme_dns cloudflare) and set CLOUDFLARE_API_TOKEN + real DOMAIN_*.
make redeploy POD=edgeThis checkout is native Linux. http://127.0.0.1:PORT is correct on the host.
If the stack runs in WSL2, a Windows browser http://127.0.0.1:PORT hits Windows, not WSL → connection refused. Use the WSL eth0 IP from hostname -I inside WSL.
See docs/LOCAL-OPS.md for the full UI port list, LLM config, JWT auth, teardown leftovers, and workflow YAML schema.
podman/pods/core/aegis-config.yaml is prefer-local:
- Host server: bind
0.0.0.0:1234(lms server start --bind 0.0.0.0). Infra comes up with no model.make teardowndoes not stop LM Studio. - From pods:
http://host.containers.internal:1234/v1 - Kali + netavark: pasta maps
host.containers.internalto169.254.1.2, which times out fromaegis-network.make deploy/make redeploy POD=corerewrite it to the host LAN IP (scripts/patch-host-gateway.sh, optionalAEGIS_HOST_LAN_IP). - Model:
qwen2.5-coder-7b-instruct(Qwen2.5-Coder-7B-Instruct Q4_K_M; confirm withcurl -s http://127.0.0.1:1234/v1/models) - Load on a 6GB GPU:
lms load qwen2.5-coder-7b-instruct --gpu max -c 4096 --parallel 1 -y. Thinking off. - Aliases
default,slm,smart,judge,slm-judgeall point at Qwen Coder 7B (max_output_tokens2048, 1024 for judge; ctx 4096) - Gemini stays
enabled: falseunlessZARU_LLM_API_KEYis set
After config changes: podman restart aegis-core-aegis-runtime, then make overlays (core start re-registers stock v1.0.0 builtins).
| Target | Description |
|---|---|
make setup |
Install Podman and dependencies (Ubuntu or Kali) |
make deploy |
Deploy all pods for the active profile |
make teardown |
Stop and remove profile pods + FUSE (not LM Studio, not leftover aegis-agent-*) |
make status |
Show running pod status |
make validate |
Run health checks against deployed services |
make registry-login |
Authenticate to ghcr.io using .env credentials |
make bootstrap-secrets |
Initialize OpenBao and populate AppRole credentials |
make bootstrap-keycloak |
Configure Keycloak realm, clients, and roles |
make generate-keys |
Generate SEAL RSA signing key pair |
make overlays |
Re-apply manifests/slow-slm/ agent/workflow versions (after core restart) |
make redeploy POD=<name> |
Tear down and redeploy a single pod |
make logs POD=<name> |
Tail logs for a specific pod |
make clean |
Full teardown + prune volumes and networks |
Copy .env.example to .env and fill in the required values. Key variables:
| Variable | Required | Description |
|---|---|---|
AEGIS_ROOT |
Yes | Absolute path to this repository checkout |
GHCR_USERNAME |
Yes | GitHub username for container registry |
GHCR_TOKEN |
Yes | GitHub PAT with read:packages scope |
POSTGRES_PASSWORD |
Yes | PostgreSQL password |
ZARU_LLM_API_KEY |
No | Gemini key; unused while Gemini is disabled |
AEGIS_HOST_LAN_IP |
No | Host IPv4 for host.containers.internal (auto-detected if empty) |
KEYCLOAK_ADMIN_PASSWORD |
Recommended | Keycloak admin password (default: changeme) |
GRAFANA_ADMIN_PASSWORD |
Recommended | Grafana admin password (default: changeme) |
CLOUDFLARE_API_TOKEN |
Edge only | Required for Caddy TLS via DNS challenge |
See .env.example for the full list with descriptions.
After make deploy PROFILE=development (which now includes the iam pod) and make bootstrap-keycloak:
# Non-interactive JWT (AEGIS_API_TOKEN is not a JWT and will 401).
# Client secret from bootstrap-keycloak.sh is "placeholder".
TOKEN=$(curl -sS -X POST 'http://127.0.0.1:8180/realms/aegis-system/protocol/openid-connect/token' \
-H 'Content-Type: application/x-www-form-urlencoded' \
-d 'client_id=aegis-runtime&client_secret=placeholder&grant_type=client_credentials' \
| python3 -c 'import sys,json;print(json.load(sys.stdin)["access_token"])')
# Direct agent execute (in-process — does NOT appear in Temporal UI)
AEGIS_KEY="$TOKEN" aegis --host 127.0.0.1 --port 8088 --output json \
agent run aegis-python-executor-agent \
--intent 'Write a Python function fib(n) that returns the first n Fibonacci numbers.'
# Durable workflow (DOES appear at http://127.0.0.1:8233/namespaces/default/workflows)
# --follow needs workflow:logs. Do not pin version 1.0.0 (stock builtin).
AEGIS_KEY="$TOKEN" aegis --host 127.0.0.1 --port 8088 --output json \
workflow run builtin-intent-to-execution \
--intent 'Write a Python function fib(n).' \
--input '{"language":"python","language_ext":"py","runner":"python3","runner_flags":"-s","container_image":"python:3.11-slim","inputs":{"n":10},"inputs_json":"{\"n\":10}"}' \
--followhello-world is deployed but cannot start (schema requires task; the execute path wraps input). Use aegis-python-executor-agent or a Temporal workflow. See docs/LOCAL-OPS.md.
| Doc | What |
|---|---|
| docs/LOCAL-OPS.md | Operator runbook (JWT, LM Studio, overlays, teardown) |
| manifests/slow-slm/README.md | Slow-SLM overlay versions and timeout knobs |
| docs/AEGIS-Local-SLM-Field-Report-2026-08-21.pdf | 2026-08-21 field report (accessible PDF + unified diff) |
| pc-tuning/summary.md | Host I/O scheduler and swappiness notes |
| AGENTS.md | Context for AI assistants (CLAUDE.md is a symlink) |
Full platform documentation: https://docs.100monkeys.ai
AGPL-3.0-only -- Copyright 2026 100monkeys.ai