Quickstart
Quickstart
Five minutes to a running agent container that can reach exactly one host and nothing else.
Linux only.
outcalldmanages a kernel network bridge and applies nftables rules — both require Linux. macOS hosts can build the workspace and run the CLI, but the daemon will not start.
1. Start the daemon
docker run -d --rm \
--name outcall-daemon \
--network host \
--cap-add NET_ADMIN \
-v /var/run/docker.sock:/var/run/docker.sock \
-v /run/outcall:/run/outcall \
-v /etc/outcall:/etc/outcall \
outcall-e2e \
outcalld --bridge outcall0Verify the bridge is up and nftables rules are active:
$ outcall bridge status
Bridge: outcall0
Status: up
Index: 12
nftables: active2. Drop in a rule
Default-block is implicit — write only the things the agent may do:
sudo mkdir -p /etc/outcall/rules.d
sudo tee /etc/outcall/rules.d/agent.yaml > /dev/null <<'EOF'
version: "1"
rules:
- id: allow-openai
description: "agent may call the OpenAI API"
condition: 'http.host == "api.openai.com"'
action: allow
egress:
mode: proxy
EOFReload the rules. There is no CLI subcommand for this yet — POST to the host API:
sudo curl -fsS --unix-socket /run/outcall/host.sock \
-X POST http://localhost/api/v1/rules/reload | jq .The response shows files_loaded, rules_loaded, and any warnings. If a
rule fails validation, the old set stays active and the error is in the
response body.
3. Create the agent network
$ outcall network create --name agent-net
Network "outcall-agent-net" created (10.200.0.0/24).Names are suffixes: --name agent-net produces a Docker network
called outcall-agent-net. The gateway is the .1 of the chosen /24
(here, 10.200.0.1) — that's also the address of the DNS filter and HTTP
proxy.
4. Run the agent
docker run -it --rm \
--network outcall-agent-net \
--dns 10.200.0.1 \
-e HTTP_PROXY=http://10.200.0.1:8080 \
-e HTTPS_PROXY=http://10.200.0.1:8080 \
-v /run/outcall/agent.sock:/run/outcall/agent.sock \
-v /usr/local/bin/outcall-agent:/usr/local/bin/outcall-agent:ro \
python:3.12 \
bash(For the all-in-one path, outcall container create --image python:3.12 --network agent-net will wire up DNS, proxy, and shim mounts for you.)
Inside the container, prove enforcement at every layer:
# Allowed
curl -s -o /dev/null -w "%{http_code}\n" https://api.openai.com/v1/models
# 401 (the request reached OpenAI, OpenAI rejected the empty auth)
# Blocked at DNS
curl -s -o /dev/null -w "%{http_code}\n" https://example.com
# curl: Could not resolve host: example.com (DNS filter returned NXDOMAIN)
# Blocked at L7 (when DNS happens to resolve from cache)
curl --resolve example.com:443:93.184.216.34 -s -o /dev/null -w "%{http_code}\n" \
https://example.com
# 403 (HTTP proxy rejected the SNI before opening the upstream tunnel)
# Blocked at L3/L4 (no proxy in the way — direct IP traffic)
nc -zv 1.1.1.1 443
# nc: connect to 1.1.1.1 port 443 (tcp) failed: Connection timed out5. Inspect what happened
outcall dns cache --entries # which hostnames the filter has seen
outcall network status --name agent-net
outcall bridge status # nftables active, bridge up
outcall proxy status # active connections, totals, blocksTo inspect rules, query the host API:
curl --unix-socket /run/outcall/host.sock http://localhost/api/v1/rules | jq .What just happened
| Step | Layer | Effect |
|---|---|---|
| 1 | host | outcalld brought up the bridge and a default-block nftables table. |
| 2 | rules | Your YAML compiled into a CEL expression and a verdict. |
| 3 | docker | A network attached to the bridge; gateway hosts DNS and proxy. |
| 4 | container | DNS, HTTP proxy, and agent shim wired up. Default-deny is in force. |
| 5 | egress | Each blocked request was rejected at the highest layer that saw it. |
Where to go next
- Writing rules — every matcher, every action, including the optional TLS-interception mode.
- Configuration — every daemon flag.
- CLI reference — every
outcallsubcommand. - Troubleshooting — diagnosing the most common failures.