OPERATIONS · v0.3.8.67
Deployment guide
Source: docs/DEPLOYMENT.md — versioned with the code.
Status: Docker, LXC, and Windows deployments are all ready to use today. Like the rest of ANTHILL they remain under continuous development and improvement — the current refinement target is deeper Windows SCM integration (see the roadmap at the bottom). This doc is the living reference for all three; it grows as each improvement lands instead of being rewritten from scratch each time.
Goal: run ANTHILL the way most home-lab/production setups actually deploy things — a standalone LXC container, a Docker container, or a Windows service — reachable at the IP of the machine it's running on, the same way any other appliance/container on your network would be. No cloud dependency, no separate reverse proxy required to make it reachable.
1. What changed to make this possible
Two small architectural changes underpin every deployment target in this doc, not just Docker:
- ANTHILL now binds all interfaces (
0.0.0.0) by default, in every safety profile. Previously the default (and every safety profile's forced override) was127.0.0.1, which meant a fresh container/service install was unreachable from the network until someone editedconfig.json. The actual security boundary was already the operator login (password auth, PBKDF2-SHA256, role-based sessions — see Security Model) and not network isolation, so this changes a cosmetic default, not the real safety rail. Setapi_hostto127.0.0.1(orANTHILL_HOST=127.0.0.1) if you specifically want localhost-only. ANTHILL_HOST/ANTHILL_PORT/ANTHILL_OLLAMA_HOST/ANTHILL_OLLAMA_MODELenvironment variables now overrideconfig.json, with the highest precedence of any config source. This is what makes container/LXC/service deployment clean: you configure the deployment (docker-composeenvironment:, an LXC profile, a Windows Service's env block) without baking settings into an image or editing a file inside a running container. It's also what makes the CLI's--host/--port/--ollama-host/--ollama-modelflags actually take effect — they set these same env vars under the hood now (previously they wrote a static field thatAnthillRuntime.Initialize()silently overwrote a moment later fromconfig.json; this was a latent bug, now fixed as a side effect).- A reachable LAN IP is auto-detected and printed at startup (
NetworkUtil.GetLikelyLanIPv4,Anthill.Core/Common/NetworkUtil.cs) — a local UDP "connect" that never sends a packet, just asks the OS which interface/address it would route through.0.0.0.0isn't a URL you can open in a browser; this is what lets the console banner andGET /status(reachable_ipfield) show you the actual address to use instead, on both Linux and Windows, without you having to runip addr/ipconfigyourself.
None of this changes the actual security posture (auth is always on; see the config-level
comments in AnthillConfig.cs/config.example.json) — it changes what a fresh install looks
like on first boot.
2. Docker (implemented)
Ships at the repo root: Dockerfile, docker-compose.yml, .dockerignore.
Quick start (Linux Docker host)
docker compose up -d --build
docker compose logs -f anthill
Watch the logs for a line like:
ANTHILL v1.8.6 API listening on http://0.0.0.0:8713
Open the colony console at http://192.168.1.50:8713/ui (or http://localhost:8713/ui on this machine)
Listening on all network interfaces — protected by the operator login, not network isolation.
Open that URL and create your admin account. That's it — no config file to prepare first.
Why network_mode: host by default
The shipped docker-compose.yml uses network_mode: "host", which makes the container share the
host's network stack directly instead of Docker's usual NAT'd bridge network. Two consequences,
both desirable here:
- ANTHILL's auto-detected reachable IP genuinely is your host machine's LAN IP — not a Docker
bridge address like
172.17.0.2that nothing else on your network can reach without a port publish. - A locally-running Ollama at
http://localhost:11434(the common home-lab setup: Ollama and ANTHILL on the same box) just works, with nohost.docker.internalplumbing.
This only works on Linux Docker hosts — it's the standard setup for Proxmox/LXC-hosted Docker, bare Debian/Ubuntu, TrueNAS SCALE, Unraid, etc. If that's not you, use bridge mode instead:
Bridge mode (Windows/macOS Docker Desktop, or if you just prefer explicit port publishing)
services:
anthill:
build: .
image: anthill:latest
container_name: anthill
ports:
- "8713:8713"
environment:
- ANTHILL_OLLAMA_HOST=http://host.docker.internal:11434
extra_hosts:
- "host.docker.internal:host-gateway" # Linux Docker Desktop / Docker Engine also needs this line;
# Docker Desktop for Mac/Windows provides host.docker.internal automatically
volumes:
- anthill-data:/app/.anthill
restart: unless-stopped
volumes:
anthill-data:
With bridge mode, ANTHILL is reachable at http://<docker-host-ip>:8713/ui — the port-published
address — rather than whatever IP NetworkUtil detects inside the container's own network
namespace (that detected value is only meaningful in host-network mode).
Persistence
Everything that needs to survive a container recreate lives under /app/.anthill inside the
container: the SQLite database, config.json (auto-seeded with container-safe defaults on first
boot if not already present), logs, backups, exports, and the AES-256-GCM field-encryption key.
The compose file mounts this as the named volume anthill-data. Back that volume up like you
would any other stateful container volume; there's nothing else to persist.
Reaching a project directory for self-modification missions
If you want the file ant to read/propose patches against a real project on disk, bind-mount it
and point agent_workspace_dir in .anthill/config.json at the same path:
volumes:
- anthill-data:/app/.anthill
- /path/on/host/to/your/project:/workspace
// .anthill/config.json (inside the anthill-data volume)
"agent_workspace_dir": "/workspace"
Upgrading
git pull
docker compose up -d --build
The named volume persists across this; only the image layers change.
Optional: a static API token for scripts/CI
Not required for normal use (the web UI creates an admin account on first launch), but if you
want one for programmatic/CI access, uncomment ANTHILL_API_TOKEN in docker-compose.yml — it
must be at least 32 characters:
openssl rand -hex 24
No native kernel in the image (by design)
The Dockerfile does not build the optional C++20 native compute kernel — ANTHILL falls back to a
bit-identical managed implementation automatically when the native library is absent (see
src/Anthill.Core/Native/NativeKernel.cs, and native_kernel: managed-fallback in /status).
This keeps the image free of a cmake/g++ build stage. If you want native-kernel acceleration
in-container, add a cmake stage before the build stage in the Dockerfile and copy the
resulting shared library into native/anthill_kernel/ before dotnet publish runs.
3. LXC (implemented)
Ships at deploy/lxc/: setup.sh (unattended installer/upgrader) and
anthill.service.template (the systemd unit it installs). Targets a fresh Debian 12+ or
Ubuntu 22.04+ LXC container — Proxmox-created or otherwise, anything with systemd and apt.
Unlike Docker, an LXC container gets a real network interface directly (no bridge/NAT layer to
work around), so NetworkUtil's auto-detected reachable IP just works here with zero extra
networking config — this is the simplest of the three targets to actually use once it's running.
Creating the container (Proxmox)
From the Proxmox host shell (or Datacenter → node → Create CT in the web UI):
# Download a template if you don't already have one (Debian 12 shown; Ubuntu 22.04/24.04 works too)
pveam update
pveam available --section system | grep debian-12
pveam download local debian-12-standard_12.*_amd64.tar.zst
# Create an unprivileged container — 2 vCPU / 4 GB RAM / 16 GB disk is plenty for ANTHILL itself
# (Ollama, if it runs on the same box, wants a lot more — see README's Resource Requirements)
pct create 200 local:vztmpl/debian-12-standard_12.*_amd64.tar.zst \
--hostname anthill \
--cores 2 --memory 4096 --swap 512 \
--rootfs local-lvm:16 \
--net0 name=eth0,bridge=vmbr0,ip=dhcp \
--unprivileged 1 \
--features nesting=0 \
--onboot 1
pct start 200
pct enter 200
No special LXC features are required — this is a plain systemd service running a normal .NET
binary, not Docker-in-LXC, so nesting, keyctl, or privileged mode aren't needed. (nesting
only matters if you're planning to run Docker or another container runtime inside this same
LXC container — unrelated to ANTHILL itself.)
Using plain LXD/incus instead of Proxmox? lxc launch images:debian/12 anthill (or the
equivalent incus launch), then lxc exec anthill -- bash gets you to the same starting point —
everything below is identical.
Installing ANTHILL
Inside the container:
apt-get update && apt-get install -y curl ca-certificates git
curl -fsSL https://raw.githubusercontent.com/Formicaria/Anthill/main/deploy/lxc/setup.sh -o setup.sh
bash setup.sh
That one script: installs the .NET 9 SDK (via Microsoft's apt repo, falling back to
dotnet-install.sh on distros/versions Microsoft's repo doesn't have an entry for yet), clones
the repo, publishes a self-contained linux-x64 binary, creates a dedicated unprivileged
anthill system user, installs and enables the systemd unit, and starts the service.
Check the result:
systemctl status anthill --no-pager
journalctl -u anthill -n 20 --no-pager # look for the "Open the colony console at http://..." line
Open the printed URL from another machine on your network and create your admin account.
Upgrading
cd /opt/anthill/src # setup.sh's default checkout location
git pull
bash /opt/anthill/src/deploy/lxc/setup.sh
Or just re-run the same one-liner from the install step — setup.sh detects the existing
checkout, pulls latest, republishes, and restarts the service. The named data directory
(/opt/anthill/.anthill by default: DB, config.json, logs, backups, exports, encryption key)
is untouched by an upgrade.
Customizing the install location / service user
ANTHILL_INSTALL_DIR=/srv/anthill ANTHILL_SERVICE_USER=anthill-svc bash setup.sh
Uninstalling
systemctl disable --now anthill
rm -f /etc/systemd/system/anthill.service
systemctl daemon-reload
rm -rf /opt/anthill /etc/anthill # or just delete/destroy the whole LXC container instead
4. Releases
.github/workflows/release.yml builds a tagged release: self-contained linux-x64/win-x64
binaries, a versioned Docker image pushed to ghcr.io/Formicaria/Anthill, and a
GitHub Release with notes pulled straight from the matching ## vX.Y.Z section of
CHANGELOG.md.
To cut a release once a version bump is committed to main:
git tag v1.8.7
git push origin v1.8.7
The workflow's first job (verify-version) fails loudly if the tag doesn't match
AnthillRuntime.Version in the pushed commit — this guards against tagging before the version
bump actually landed. If it fails: bump the version, commit, delete the bad tag
(git tag -d v1.8.7 && git push origin :refs/tags/v1.8.7), and re-tag.
The release and the container package are published automatically on tag push — no manual
"Publish" step. Each tag produces: the self-contained linux-x64/win-x64 archives attached to
a published GitHub Release (marked "latest"), and ghcr.io/<repo>:<version> + :latest pushed to
GitHub Packages (GHCR). Four-part maintenance tags (vX.Y.Z.W) are handled the same as three-part
releases.
First run only: GHCR creates the container package as private by default. Make it public once
at github.com/users/<you>/packages/container/operation-anthill/settings → Change visibility so
docker pull works without a login.
CI artifacts (every run, no tag needed)
Since v1.8.23.3, plain CI (.github/workflows/ci.yml) also produces a package: after the
linux-x64 publish + --selftest job succeeds, the publish output is archived as
anthill-linux-x64-v<version>.tar.gz and uploaded as a workflow artifact. Grab it from
Actions → (the run) → Artifacts. This gives every green PR/main run a downloadable,
release-ready build without cutting a tag; artifacts expire per GitHub's retention policy
(default 90 days), so tagged Releases remain the permanent distribution channel.
Operator-shell service control (polkit)
The systemd unit runs with NoNewPrivileges=true, which blocks sudo/setuid escalation — so the
admin-only operator Shell console (running as the anthill service user) can't sudo systemctl restart. setup.sh therefore installs a scoped polkit rule
(/etc/polkit-1/rules.d/49-anthill.rules, from deploy/lxc/anthill-polkit.rules.template) that
authorizes the service user to manage only the anthill.service unit (restart/stop/start/
status) over D-Bus — systemd performs the action, so no privilege escalation is needed and the
unit's hardening is untouched. The Shell tab's "Restart service", "Service status", and "Recent
logs" buttons use it. Nothing else is granted — no other units, no package or system management;
upgrades still run bash deploy/lxc/setup.sh from a root shell. If polkit isn't installed the rule
is skipped (the installer says so) and service control from the console won't be available.
5. Windows Service (ready — refinements ongoing)
Windows deployment is ready to use today: README.md documents registering the published
anthill.exe directly via New-Service/sc.exe (Deploy on Windows → Option C) — start, stop,
and automatic startup all work. Like the other deployment paths, it's under continuous
improvement. The current refinement target — graceful shutdown on service stop, correct
integration with the Service Control Manager's startup/shutdown timeouts, Windows Event Log
output instead of a console — is wiring up
Microsoft.Extensions.Hosting.WindowsServices
(.UseWindowsService() on the host builder in src/Anthill.Api/ApiHost.cs, plus a new
Microsoft.Extensions.Hosting.WindowsServices package reference). That package restore needs to
happen on a machine with real NuGet access; this doc will be updated with the install script once
it's built and verified.
6. Roadmap
| Target | Status | Key files |
|---|---|---|
| Container-style IP binding + env var overrides ✅ | DONE. Underpins all three targets below. | NetworkUtil.cs, AnthillConfig.cs, AnthillRuntime.cs, Anthill.Cli/Program.cs, Anthill.Api/ApiHost.cs |
| Docker ✅ | DONE. Dockerfile, docker-compose.yml, .dockerignore at repo root. |
see §2 above |
| LXC ✅ | DONE. deploy/lxc/setup.sh + anthill.service.template. |
see §3 above |
| Tagged releases ✅ | DONE. Binaries + Docker image (GHCR) + published GitHub Release, all automatic on tag push. | .github/workflows/release.yml, see §4 above |
| Windows Service ✅ | READY via New-Service/sc.exe registration (see §5). Refinement in progress: UseWindowsService() SCM integration + install script. |
Microsoft.Extensions.Hosting.WindowsServices integration in ApiHost.cs, install script |
Implementation order: container-style networking (done) → Docker (done) → LXC (done) → Windows Service (ready; SCM refinements ongoing), per the agreed build order.