Changelog
Notable changes to Ant.
Ant is pre-alpha. Every push to main is tagged vX.Y.Z (a patch bump from
the previous release) and published to the download host.
No tagged releases yet.
Unreleased
Added
- Rollback:
ant trail rollback [deployment] [--to RELEASE] [--steps N]redeploys a previously released image without rebuilding, with optionalrollback.pre_hook/rollback.post_hook;rollback.keep_releasesis the fallback for image retention. - Audit log: a tamper-evident, hash-chained log of privileged RPCs on each
machine, read with
ant nest audit(rotates at 8 MiB; admin-only). - State backup/restore:
ant nest state export | import(owner-only) backs up and restores a machine's roster and invites. - Cleanup:
ant nest doctor --cleanupremoves client leftovers (pre-v2 config backup, world-readable deploy logs, empty~/.ant/localtrees) and, on a machine, stored projects whose workload is gone. - Machine decommission:
ant nest machines decommission NAME --host … --yesreverses provisioning over SSH (--purge,--keep-record). - Tunnels:
ant nest tunnel --container NAME --port Pforwards a local port to a container port on a machine over iroh; the dashboard's remote machine page has the same action on a running container's row (the listener stays on the dashboard's host). - Secret providers: a secret may name
from: op://…,cmd:…, orenv:…, resolved at deploy time so the value is never stored. - Dashboard password:
ant ui passwdsets the password required for a non-loopback bind; a successful login issues a signed session cookie. - Project templates:
ant nest templates list | search | show | add | syncandant nest new, backed by the Dokploy blueprint catalog (bundled snapshot plus live fetch). - Image signing: opt-in cosign verification via
deploy.image_signing; the worker verifies on the machine before pulling and running, not just the local CLI. - Branch-optional naming:
naming.branch: falsekeeps one set of resources per project+env across branches. - Dashboard service:
ant ui install | status | start | stop | restart | recreate | logs | passwd | run | uninstallruns the console as a background service (a systemd user unit on Linux, a launchd agent on macOS). - Domains are opt-in: ant never adds a hostname. Each
deployments.<env>.domainsentry names its routing target (a compose service/port, a path, or a bare port) and is added withant nest edit domain addor the dashboard's Domains tab. - Group base domains: a group carries a list of base domains; a deployment
adds a subdomain or path, uses one as-is, or pins one with
group_domain. - Machine-wide network: opt in per project with
deploy.use_machine_network; the machine's worker ensures and attaches it. - Docker volume management:
ant nest volume list | rm, aGET /api/volumesandDELETE /api/volumes/{name}endpoint, and a Volumes section on a machine's dashboard page. Volumes report whether a container mounts them. - Colony profiles: edit a member's name, email, or linked unix account
(
ant colony users update,ant colony profile). - Remote machines:
ant-worker(fromcmd/agent) with per-RPC authorization, signed single-use (and revocable) invites, and remote deploy over the iroh transport. - Web dashboard parity: the roster backend is shared between the CLI and the dashboard, so users, roles, and deploys agree in both.
- Deploy-time registry credentials: with
ANT_REGISTRY_PASSWORDset, the pull credential travels with the deploy (deploy.run,compose.apply,stack.apply;ANT_REGISTRY_USERNAMEcovers a registry-qualified--image) as a short-lived worker-sideDOCKER_CONFIG, so private images need no storeddocker loginon the machine. - Handover Caddyfile:
decommission --handoverexportscaddy/Caddyfile.ant-handoverwith the routes ant programmed, so domains keep resolving after the takeover.
Changed
~/.antlayout: a worker's state moved under~/.ant/worker/(identity, state, audit log, remote-compose) and the managed local Caddy's PID under~/.ant/caddy/; a default-path worker adopts pre-existing files on first run, and explicit--identity/--stateflags are untouched.- Dashboard look: the console wears the ant palette (warm paper in light, warm charcoal in dark, the logo's wood amber as the single interactive accent); the home stat cards became one status strip, and the theme control follows the applied theme, system included.
- Project page and settings: the project header groups its actions by intent (environment, Build → Deploy, a joined Restart|Stop, quiet Terminal and settings icons, and a menu for rollback/destroy); page tabs are one underlined rail on the project, group, machine, and settings pages; and settings/detail cards share one titled section pattern.
- Machine and project details: remote machines can be renamed from the machine page; the project overview lists what each compose service runs (or that it builds on deploy); volume rows use Key/Value plus the container path; and editing an environment shows the compose file it uses, overridden or inherited.
Changed (breaking)
- CLI surface cleanup. Machine resources moved to
nest(ant nest containers,ant nest volume) andant trail pswas removed;colony users promotebecamecolony users update --role; the hiddenant foragealias was retired. Config-target flags use--target-machinenow, so--machineis routing-only and local-only commands fail fast on a remote. - Wire shape: deploy payloads. Deploy-path payloads are shared types on both
sides, and
route.applycarries a route spec (service + port) the worker resolves. The pre-release protocol version stays v1; upgrade the CLI and every worker together, since mixed versions fail closed. - Wire shape: compose profiles. Compose profiles and external-network semantics ride on the deploy payloads.
Fixed
- Project page environment scoping: the environment picker scopes the header status, badges, image, and Monitoring (containers, logs, disk usage on that environment's machine) to the selected deployment; a remote environment reads its containers from its own machine instead of showing the local deployment's running state.
- Local container secrets. The default local
run: containerpath dropped provider-resolved secrets entirely; it now passes them like the other paths. - Cancelled deploys. A cancelled running probe no longer leaves the app
stopped;
ContainerRunningerrors abort the swap. - Branch naming works when the project lives in a subdirectory and the
default relative
--config ant.yamlis used. - Swap leftovers (
-staging/-previous) are removed only when their app label matches, so a colliding resource name from another app is not deleted. - Zero-downtime ports preserve an explicit host IP (including loopback).
--machine:trail logs,trail rollback, andhaulhonor--machine, and an explicit--machine localfails fast when the config targets another machine.nest editcomputes env/secret/volume/network list mutations under the config lock and stores an absolute project root for groups.- Health checks with an invalid path return an error instead of panicking.
- Archive extraction caps the entry count.
- Provisioning keeps a corrupt machine identity instead of regenerating it, and removes the upload directory on every path.
- History and logs:
history --clear --machinekeeps other machines' entries, remote logs resolve the deployment'srun:, andhaulrecords the machine it built on. - Managed Caddy uses the configured
caddy.admin_listen, keeps its config in~/.ant, stops a live managed process before starting another, and removes a PID file only when it names the exiting process. - Remote compose deploys.
deploy.fileslayers are all resolved (previously only the first was shipped),deploy.profilesactivate the same services remotely as locally,deploy.build_services: falseno longer rebuilds anyway, and runtime secrets are no longer interpolated to empty (or baked) on the client before the worker'ssecrets.envapplies. - Release ids. A remote source build without
--releaserecords the minted image tag, so history androllback --tocan address the deploy. trail destroyno longer reports success when the container removal fails, and a non-path-safeapp:value is refused instead of deleting a directory outside~/.ant/local.- Caddy routes. Local deploys validate route paths (no wildcards), path routes are ordered before a host-only route, and conflict checks cover every host in a match set.
- Config. Legacy group
machine:values are normalized during migration, machine identity paths normalize their id, concurrentEditFilewrites are serialized, deployment keys that normalize to the same name are rejected, a newer schema version is refused instead of silently downgraded, and config writes follow a symlinked config to its target. - Machine ops. A failed container-existence probe aborts a deploy instead of
deleting the live app, the stop/park step survives a cancelled RPC, the reaper
restores a parked container when the canonical one exists but is not running,
and a bad network name no longer leaks a secret env file in
/tmp. - Rollback no longer fails on an unavailable build-secret provider, compose
config writes restore the previous file when the secrets file cannot be
written, and
nest initquotes generated YAML values. - Remote compose domains. A remote compose deploy now programs its domains
after
upinstead of dropping them silently; a routing failure fails the deploy with the stack left running. - First-deploy health gate probes a first-time container (previously only staged replacements were gated), and a failed first deploy removes the half-created container.
- Teardown clears routes on
compose.down/containers.removeand localtrail down/destroy, so a domain does not keep pointing at a removed app. - Buildpacks and railpack builds work:
packno longer gets an unsupported--label, and provisioning starts BuildKit when railpack is installed. - Provisioning installs the Docker Compose and buildx plugins when the distro ships them separately; a missing plugin now fails with a clear message.
- Local Caddy listener changes: changing
caddy.http_listen/https_listennow reaches a running managed Caddy: the next local deploy restarts it, andant nest tools caddy restartapplies it on demand. The old behavior silently kept the previous listeners. - Local Caddy without a PID file:
ant nest tools caddy stop|restartnow falls back to Caddy's admin API, so it works on a proxy started by an earlier ant invocation (or one whose PID file was cleaned). - Tool status:
ant nest tools <tool> status [--json]reports any tool (installed, version, path, install hint; Docker also reports daemon reachability, so a down daemon is not shown as "not installed"). Only the one tool ant runs as a service, caddy, additionally getsant nest tools caddy restart|stop; the builders are one-shot CLIs and the Docker daemon is host-managed, so they have no lifecycle verbs. - Per-deployment target and secrets:
trail deploy,trail status, and the rollback history honor a deployment'sserver:override (andtrail status --machine MtargetsM);deploy.publishmerges per deployment; and editing secrets keeps eachfrom:provider reference. - Local compose secrets are no longer written into a
.ant-<app>.ymlfile in the project directory; the patch uses${KEY}placeholders and the values pass through the compose process environment. - Interrupted-deploy reaper no longer mistakes a compose service whose
container_nameends in-staging/-previousfor a leftover swap container. ant.yamltypos are refused instead of silently ignored: unknown keys and a newerversion:fail the load, so a misspelledzero_downtime,health_check, orimage_signingcannot disable the feature it names.- Zero-downtime cleanup runs on a detached context and reports failures as deploy warnings, so a cancelled RPC cannot leave the old container holding the canonical name unnoticed.
- Deploy probes report a failed
ContainerRunningas a probe error, abort cancellation promptly, and remove a container whose first-deploy health gate failed. - Docker reads on the worker keep a per-call timeout, so an unresponsive daemon cannot pin a container, image, or volume listing RPC.
- Service units escape a literal
%for systemd and write units/plists atomically; a corrupt deploy history is backed up instead of overwritten; malformed legacy config values (including group entries) are reported instead of dropped; and unknown secret providers no longer echo the fullfrom:reference. - Remote nests in the dashboard no longer fail with "Remote worker: context deadline exceeded": the dashboard shares one iroh endpoint per process instead of dialing per request, remote probes get individual slices of a bounded page budget, and page polls never start a new request before the previous one settles.
- Last owner: the machine's sole owner can no longer be demoted or removed (dashboard and daemon), and the settings page lists group scan roots next to the machine's own so group-registered projects are visible.
- Compose volume edits keep mount modes beyond
roand preserve tmpfs / option-carrying long-syntax mounts verbatim; a volume row's Key and Value are mutually exclusive instead of silently dropping one. - Stack routing warns when a routed service publishes in
mode: hoston a multi-node swarm, where a manager-local Caddy cannot reach the task.
Security
- Dashboard CSRF over GET. Cross-site requests to the dashboard's API are
rejected for every method, GET included: a malicious page could otherwise
trigger
GET /api/projects/{name}/exec(which runs a command in a container). Opening the UI itself from another site (a top-level navigation) still works. - Container env passthrough. Runtime secrets (including multi-line values)
travel only in the docker process environment on both local and remote
container deploys; the temporary
--env-fileis gone. - Compose
~. The mount/read guards refuse a leading~, which compose expands to the user's home directory. - Transport lifetimes. Per-connection idle deadlines, tunnel first-stream deadlines, and oldest-connection eviction bound connection-slot exhaustion; a split read/handler budget stops empty streams from blocking RPC processing.
- Caddy routes are serialized in-process.
- Denial audit has a global per-interval cap; invite redemptions and failed tunnel authorizations are recorded.
- System accounts derive from 12 NodeID characters; duplicate links are refused.
- Resource-name collisions are refused instead of replacing another app's container.
- Cloud-init escapes values for the single-quoted systemd unit.
- Build-context extraction. A symlink whose target traverses an earlier symlink is refused, and the remote Dockerfile write removes an existing symlink first, closing an arbitrary-file-write path.
- Compose guard.
env_file,buildcontext,extends, andincludepaths are checked on the worker (client-side parent-relative paths stay allowed); a remote apply refuses build services outright. - Local container secrets are passed through the docker process environment
instead of
docker run -e KEY=VALUE, so they no longer appear in argv. - Worker bootstrap uploads into a private
mktemp -ddirectory instead of predictable/tmpnames a local user could pre-create. trail prune --volumesremoves only the project's unused volumes, scoped by its compose label, and refuses to run without a project; previously it removed every unused volume on the host.- Transport hardening. Per-peer connection caps and a first-stream watchdog bound connection-slot exhaustion; the endpoint no longer hangs forever waiting for a relay; an oversize error reply is truncated rather than dropped.
- Managed Caddy PID file moved out of world-writable
/tmp, and a PID file with no recorded binary is never signalled. --machineon local-only trail commands (down,restart,destroy,env,exec) now fails fast, andtrail deployhonors it as a routing override.- Denial audit entries are rate-limited per peer+method, so an unauthenticated caller cannot force log rotation.
- App names are validated at config load, and unknown
users.removeops are rejected instead of deleting the roster row without a plan. - Build-context extraction can no longer be redirected outside the extraction root by a chained symlink.
- Container-deploy mounts refuse relative sources (they resolved against
the worker's working directory), ancestors of the docker socket, and compose
volumes_from; the identity directory and~/.antare protected even when--statepoints elsewhere. - Audit log flooding is bounded: details are capped, unauthenticated request bodies are not logged, oversized lines are skipped on read, and two rotated segments are kept.
- Route upstreams are verified against the named container's published ports, so a deploy cannot route the Caddy admin API or another local listener.
- Role changes are re-checked against the state being mutated, closing the window where a demoted caller finished an in-flight privileged call.
- Worker stream budgets stop a stalled or trickled request and bound every call's duration, so a hung command cannot pin a handler slot.
- Mount-path resolution fails closed: a bind source that cannot be resolved (an untraversable parent, a symlink loop) is refused, since the root-owned docker daemon would follow it.
- Invite revocation re-resolves the caller's role under the write lock, so a demoted member cannot finish an in-flight revoke with its stale role.
Changed
- Caddy is required on a remote nest (optional locally); a worker with no reachable Caddy still starts (logging a warning) but refuses deploys until Caddy answers.
- Config schema v2:
project_dirs,groups, andcaddynest undermachines.<id>; v1 configs migrate on load. - Resource naming: resources are named
[<group>-]<app>-<env>[-<branch>]from one rule (branch can be dropped withnaming.branch: false). - Machine resources moved:
containersandvolumelive underant nest;ant nest deployis nowant trail deploy --image. - Mount guard: deploys that reach the worker's identity/state (including its ancestors), the docker socket, or the host root are refused.
- Log color: CLI and dashboard logs share one heuristic: errors red, warns amber, success emerald, steps cyan, debug gray.
Removed
- Project-wide
expose: routing lives per deployment underdomains:, so each hostname is paired with what it forwards to.
Pre-alpha
See the installation guide for release tarballs,
checksums.sha256, and the rolling main build.