# Changelog All notable changes to incus-compose are documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). Version numbering moved from `0.0.1` to `1.0.0` at beta11 (1.0.0 is the intended final version), and the beta suffix gained a dot (`beta.16`) from beta.16 onward for correct semver ordering. Headings below preserve each release's announced form. ## [Unreleased] ### Added - Support for the Compose Deploy Specification (`deploy.mode`, `deploy.replicas`, `deploy.placement.constraints`, `deploy.placement.preferences`, `deploy.placement.max_replicas_per_node`, `deploy.restart_policy`, and `deploy.update_config` rolling updates) across Incus cluster members. Scaling or multi-node placement on a cluster requires a remote/shared storage pool unless overridden with `--allow-local-storage` (`INCUS_COMPOSE_ALLOW_LOCAL_STORAGE`) or `x-incus-compose.allow-local-storage: true`. (by @jochumdev, #1) - Instances carry a `user.label.incus-compose.config-hash` label with the hash of their service. `up` recreates the drifted ones, `config --hash` prints the hash of the current file, and `ps` marks changed instances (`config_changed` in JSON). (by @tallica) - Support for declarative Network ACLs (firewall rules between compose networks) via `networks..x-incus-compose.acl` (`default`, `ingress`, and `egress` rules). Supports OVN-to-OVN network peer resolution (`@/`) as well as bridge subnet CIDR resolution across same-project and cross-project (`:`) network references. (by @jochumdev, #98) - Disk and tmpfs mount targets that are symlinks in the container rootfs (common in `nix-containers` images such as `/etc/caddy` pointing into `/nix/store`) are now materialized as real directories or files before starting the instance, and recursive directory creation when pushing files now follows existing symlinked parent directories. (by @jochumdev) - Support for custom image builders and built-in `nix2container` builds via `build.x-incus-compose` (`builder` and `args`), allowing container images to be built directly from `nix2container` flake targets or external builder binaries without Docker or Podman. (by @jochumdev) - Support for OVN networks as the default network driver for newly created projects when Incus supports OVN. Includes `--network-driver` / `INCUS_COMPOSE_NETWORK_DRIVER` and `--network-uplink` / `INCUS_COMPOSE_NETWORK_UPLINK` flags on `up`, top-level `x-incus-compose.network.driver` (`auto`, `ovn`, `bridge`) and `x-incus-compose.network.uplink` compose options (with backward-compatible support for `x-incus-compose.network-driver`), and per-network `x-incus-compose.uplink` (or `parent`) extension to configure uplink networks. Existing projects and servers without OVN continue to use bridge networks. (by @jochumdev) - Service-name and alias DNS resolution now works on OVN networks. incus-compose peers each project network to the shared `ic-dns` and scopes it with a hidden network ACL, so service names and network aliases resolve with no compose change. Network aliases are assigned to the first replica when scaled to prevent contested collisions in `ic-dns`. OVN networks also get a default ACL posture matching docker compose: instances on the same network reach each other, nothing else may initiate in, outbound is allowed. (by @jochumdev) - `--no-dns` / `INCUS_COMPOSE_NO_DNS` flag on `up` and `down`, and `x-incus-compose.dns.disabled` compose option to opt out of DNS sidecar creation/configuration on `up` and stop/removal of project-scoped sidecars on `down`. (by @jochumdev) - `ic-dns`: A new split-horizon authoritative DNS daemon for Incus instances, built on the new `ievent` event framework and CoreDNS. Resolves instance names dynamically within per-project or shared zones (`.incus`), serving records based on querier network visibility and client subnet (RFC 7871 ECS). Supports UDP and TCP on port 53, zone transfers (`--allow-transfer`), upstream forwarding (`--forward`), configurable TTL, and Prometheus metrics and health endpoints on `:9153` (or `--http`). Projects opt in via `--project-marker` (defaulting to `user.label.dns.scope=global`), an explicit `--project` list, or serve all visible projects with `--project-marker ""`. (by @jochumdev) - `ievent`: A pluggable event pipeline framework for Incus lifecycle events, processing daemon event streams through ordered, composable plugins (`source`, `debounce`, `enricher`, `checker`, `dns`, `http`, `log`). (by @jochumdev) - ic-healthd serves `/metrics`, `/health` and `/ready` on `:9153`; an empty `--http-address` / `INCUS_COMPOSE_HEALTHD_HTTP_ADDRESS` disables it. (by @jochumdev) - `--metrics` / `INCUS_COMPOSE_HEALTHD_METRICS` flag to record Prometheus metrics in ic-healthd (defaults to true). (by @jochumdev) - Running ic-healthd by hand can now present an already-trusted certificate (`--client-cert` with `--client-key`) or connect as a remote from the Incus CLI configuration (`--remote` with `--use-remote`). (by @jochumdev) - Support for Compose Specification lifecycle hooks (`pre_start` and `post_start`) on services. `pre_start` runs ephemeral init containers sharing the service's volumes and tmpfs mounts before the main service starts, supporting `command`, `image`, `user`, `privileged`, `working_dir`, `environment`, and `per_replica`. Runner containers are deleted on success and retained stopped on failure for inspection. `post_start` executes commands non-interactively inside the running service container after startup and before healthcheck evaluation, supporting `command`, `user`, `privileged`, `working_dir`, and `environment`. (by @jochumdev) - Configurable tools volume, mount path, and sleep helper image options on `client.GlobalClient` (`ClientToolsVolume`, `ClientToolsMount`, `ClientSleepImage`). (by @jochumdev) ### Changed - `healthd status` now format output with status, IPv4 and IPv6 addresses, and optional Prometheus metrics queried directly via `incus port-forward` and HTTP, add `--port` to override the HTTP port (default: 9153), `--format ` (default: `text`), and `--metrics` to include Prometheus metrics in the report. (by @jochumdev) - The global network `icompose0` now configures DHCP ranges after creation to reserve addresses for static IP assignment, and the global DNS sidecar is assigned a static `.53` address within that reserved range. (by @jochumdev) - Require incus 7.0.2+ LTS or 7.5+ both unreleased at the time of writing, we need various recent bugfixes (by @jochumdev) - ic-healthd is migrated to the `ievent` framework: one chain of plugins replaces its listener, router and per-project schedulers. The sidecar contract is unchanged - the same flags, environment variables and status writes, and `healthd reload` still forces a full resync. (by @jochumdev) - Refactor the `run` command in `cmd/incus-compose/run.go` to use `client.Client` abstractions (`c.EnsureTools()`, `c.Config().ToolsMount`, `c.Config().SleepImage`, and `c.Resource()`) instead of raw Incus API and `iclient` calls. (by @jochumdev) - Remove `--healthd-image`, `--sleep-image`, and `--dns-image` CLI flags from subcommands (`up`, `run`, `pull`, `healthd up`/`down`, `dns up`). Images are resolved centrally on initialization. (by @jochumdev) ### Removed - `ic-healthd`: Remove deprecated `user.healthcheck.ignore` (`shared.HealthIgnoreKey`) tag and check; instance watching is strictly opt-in via `user.healthcheck.enabled`. (by @jochumdev) ### Fixed - Bridge network creation on Incus clusters now uses Incus's two-phase per-member pending network workflow (`?target=`) and co-locates single-replica services and their volumes onto the connected cluster member when using a bridge network or member-local storage pool. (by @jochumdev, #213) - Bridge network `raw.dnsmasq` now includes `local=//` alongside `address=//` entries so `AAAA` lookups for IPv4-only service names are answered locally with `NODATA` instead of being forwarded upstream and timing out. (by @jlutran, @jochumdev, #236) - Removing a network ACL while other services change the same OVN network's ACLs no longer fails with `Failed removing default routes: object not found`. (by @tallica) - `logs [SERVICE...]` and foreground `up [SERVICE...]` now filter log output to the in-scope services (including scaled replicas) and reject unknown service names. (by @jochumdev, #231) - On a server without OVN, `up` and the `healthd`/`dns` commands no longer spend about two seconds detecting that, several times per command, and OVN networks in projects other than `default` are now found. (by @tallica) - `build` now works on macOS instead of erroring with "the build command is only available on linux". (by @tallica) - `ic-dns` now learns of a bridge network as soon as it is created, so an instance on a new bridge is enriched with its interfaces on hosts without OVN. (by @tallica) - Taking the global lock gives up after five failed attempts with the underlying error instead of retrying silently for ever. (by @tallica) - The client's project cache and hooks are safe to use from parallel workers; a race could drop a cached project or a debug hook. (by @tallica) - Taking an image lock creates the global project when it does not exist yet, so a fresh server no longer hangs there. (by @tallica) - `ic-dns` drops a deleted or renamed network when it happens instead of at the next sweep. (by @tallica) ## [v1.3.5] - 2026-10-17 ### Fixed - Network `aliases` are now flushed to `raw.dnsmasq` when all services in a project set `container_name` equal to their service key. Previously, DNS alias flushing was only triggered during instance address registration, which was skipped when the container name matched the service name. (by @Tofil, @jochumdev, #206) - `down --rmi` now validates its argument (`local` or `all`), preventing subsequent flags (such as `--volumes`) or invalid values from being silently consumed. (by @pikeas, @jochumdev, #220) - `down` now validates service names passed as arguments against declared services in the Compose project, returning an error if a service does not exist rather than silently doing nothing. (by @pikeas, @jochumdev, #221) - Containers on Incus < 7.6 now set `lxc.environment.hooks` via `raw.lxc` to override `PATH` for LXC hooks running on the host, fixing containers whose image `PATH` does not match the host's (such as `ghcr.io/nix-containers/images/*`). (by @jochumdev) ### Added - Support for direct Incus connections via `--server-url` (`INCUS_SERVER_URL`), `--client-cert-file` (`INCUS_CLIENT_CERT_FILE`), `--client-key-file` (`INCUS_CLIENT_KEY_FILE`), and `--server-fingerprint` (`INCUS_SERVER_FINGERPRINT`). An explicit `--remote` flag takes precedence over manual connection settings; otherwise, manual connection takes precedence over `INCUS_REMOTE` or the default Incus remote. (by @jochumdev) - Support for pass-through bind mounts on remote Incus servers via the inline volume extension `x-incus-compose.remote: true` or the global `--allow-remote-mounts` (`INCUS_COMPOSE_ALLOW_REMOTE_MOUNTS`) flag. Both skip the same-host and local `os.Stat` checks so the source path is resolved on the remote Incus host. (by @jochumdev) - External networks referencing an unmanaged host network (such as an unmanaged bridge `br0` where Incus reports `MANAGED: NO`) are now automatically translated to `nictype: bridged` (or `physical` / `macvlan`) with `parent: ` on the instance device instead of `network: `, allowing direct attachment to unmanaged host bridges via standard compose `networks:` declarations. (by @jochumdev) ### Changed - Seeded directory bind mounts (`x-incus-compose.seed: true`) now re-seed the volume on every `up`, overwriting matching files in the target while leaving other files in the volume untouched. (by @jochumdev) ## [v1.3.4] - 2026-09-17 ### Added - Compose network `ipam.config` pools are now supported. Subnet and gateway definitions automatically calculate the corresponding Incus `ipv4.address` and `ipv6.address` gateway CIDRs on custom networks, with explicit `x-incus` network extensions taking precedence when specified. (by @jochumdev) ### Fixed - Healthd registration token storage moved from `/run/secrets/token` to `/secrets/token` for Incus 7.0.1 LTS users, where `/run` tmpfs mounts masked the token file and caused certificate registration to fail. This temporary workaround will be removed in v1.4.0. (by @jochumdev, #201) - `list` now logs the error that ensuring the stack returned, instead of the literal text `error=err`. (by @pikeas) - Image builds now clean up their temporary rootfs tar archives in `/tmp` when the build finishes or fails. Previously, the deletion call on close was commented out, leaving multi-megabyte `incus-compose-rootfs-*.tar` files behind in the system temporary directory after every build. (by @jochumdev) - `up --recreate ` now brings back reverse dependencies that were torn down during the recreate down phase. Previously, `up --recreate` on a service without `--no-deps` tore down services that depended on it, but the subsequent ensure phase only scheduled the named service and its forward dependencies, leaving dependent instances missing. (by @jochumdev, #192) - Healthd teardown now executes all cleanup stages (stop, delete, and certificate revocation) even if an earlier stage fails, and `healthd down` ignores expected benign errors for resources that are not running, not ensured, not found, or still in use by other replicas. Previously, an error during the stop phase aborted teardown immediately, leaving healthd containers or certificates behind. (by @jochumdev, #195) - `working_dir` on a service now sets the instance's `oci.cwd`; the image's WORKDIR no longer always wins. `run` without `--workdir` starts there too. (by @sandroden) ## [v1.3.3] - 2026-09-07 ### Fixed - `up --recreate` no longer reports success when deleting an existing instance fails. `down()` previously logged deletion errors as warnings and returned `nil`, causing the subsequent ensure step to accept the still-running instance and exit 0 without recreating anything. A failed deletion during recreate now aborts the run with an error, while standalone `down` remains best-effort. (by @alien43) - A network attachment with `internal: true` now correctly sets `ipv4.gateway` and `ipv6.gateway` to `none` even without a static address. Previously, gateway overrides were only written when a static IP was defined, allowing services without static addresses on internal networks to keep default routes and unintended outbound network access. (by @alien43) - Auto-volume generation now recognizes mount paths covered by custom disk and tmpfs devices declared in `x-incus-compose.devices`. Previously, mount paths on extra devices were not populated on the typed structs, making them invisible to path deduplication. This caused duplicate storage volumes to be created for image-declared `VOLUME` paths, failing instance creation with `More than one disk device uses the same path`. (by @alien43) - Service-name DNS registration (`raw.dnsmasq`) is now skipped on non-bridge networks such as OVN. Because `raw.dnsmasq` is a bridge-only configuration option, attempting to write it on OVN networks caused `up`, `start`, and `stop` commands to fail, preventing projects attached to external OVN networks from converging. (by @sandroden) ## [v1.3.2] - 2026-08-30 ### Changed - A service whose image ships a Dockerfile `HEALTHCHECK` and whose compose file says nothing about health now inherits that check, as `docker compose` does. The service is watched by ic-healthd, so with a restart policy set a check nobody reviewed can restart it, and a `depends_on: condition: service_healthy` on it now resolves on the image's check rather than on its run state. Opt out with `healthcheck: {disable: true}`, which is also honoured now, or with `user.healthcheck.enabled: "false"` via `x-incus`. The image's own `HEALTHCHECK NONE` is honoured the same way. Images already in the cache are not re-read, so this reaches an existing stack only on a fresh pull, and only when the instance is created. (by @jochumdev) ### Fixed - `healthcheck: {disable: true}` no longer enrolls the service for health checking and writes a `null` test command. The compose spec's own way to say "no check" was read as a check being declared. (by @jochumdev) - ic-healthd runs a healthcheck as the image's own process - its working directory, user and group (`oci.cwd`, `oci.uid`, `oci.gid`) - instead of as root in `/root`, which is where an `exec` lands. A test using a relative path failed outright, and one depending on the service's user could pass as root where it would not have for the service itself. Both are normal ways to write a test, because that is how docker runs them. (by @jochumdev) ## [v1.3.1] - 2026-08-27 ### Fixed - `cp`, `top` and `events` are actually there. v1.3.0 documented and announced all three, but the commit adding them never landed in the release. (by @jochumdev) ## [v1.3.0] - 2026-08-25 ### Added - `services.{name}.x-incus-compose.profiles`: set the instance's full Incus profile list on create, verbatim - same semantics as `incus launch --profile`, so a list that omits `default` leaves the instance without it. Until now nothing in compose could express profile _membership_: `x-incus` merges into the instance's config map, and `profiles` is a field on the instance struct, not a config key. (by @alien43) - `run SERVICE [COMMAND]` starts a one-off instance and exits with the command's own status. One-off instances carry `user.incus-compose.oneoff=true`: `up` never reconciles them, `ps` lists them under their service, `down` removes them without `--rm`, and ic-healthd never restarts one. The command runs through an exec into a blocking helper, so it is not PID 1; `pull` and `up` prefetch that helper, which is the only step needing the network, so an air-gapped site can `run` later. `--sleep-image` / `INCUS_COMPOSE_SLEEP_IMAGE` / `x-incus-compose.init` point it at a mirror. A cluster mixing CPU architectures is not supported. (by @jochumdev) - `pause`, `unpause`, `kill`, `cp`, `top`, `events` and `port`, matching their `docker compose` counterparts. A pause also sets `user.healthcheck.stopped`, since a paused instance answers no healthcheck and ic-healthd would restart out of the pause. `kill -s` takes only SIGKILL, in docker's three spellings: the Incus state API carries no signal. `cp` decides which side is the instance by the name before the colon, so a Windows drive stays local. `top` reports per instance where docker reports per process. (by @jochumdev) - Two commands docker compose has no counterpart for: `port-forward SERVICE TARGET_PORT [LISTEN_PORT]` runs a local TCP listener and forwards into the instance, reaching a port that was never published - it needs Incus 7.3 or 7.0.1 LTS - and `healthd status` prints the shared daemon's health status key. (by @jochumdev) - `backup` copies a project's named volumes into a separate `-backup` project with per-run restore points, where `down --volumes` and `down --project` cannot reach them. `create`, `list`, `verify`, `restore` and `delete --keep-last N`; the pool comes from `x-incus-compose.backup.pool`. (by @ishaan-jindal and @jochumdev) - Volumes are filled from the image. Every path an image declares as a `VOLUME` gets a storage volume of the service's own, instead of the tmpfs that lost its contents on restart, and a named volume starts from what the image ships at its target - `conf:/etc/nginx/conf.d` is no longer empty on the first run. `x-incus-compose.auto-volumes: false` and `volume: {nocopy: true}` turn each off. (by @jochumdev) - An external network can name `:` to attach to a managed network owned by another compose project. (by @jochumdev) - ic-healthd is published for `ppc64le`, `s390x` and `riscv64` besides `amd64` and `arm64`. (by @jochumdev) ### Changed - `stop`, and with it `restart`, shuts a service down gracefully and kills it once `--timeout` is up. Both killed outright before, so `--timeout` did nothing at all. `kill` is the old behaviour under its own name. (by @jochumdev) - `--pull always` only re-fetches an image the registry moved, rather than dropping every registry image and downloading it again per run, and `up --pull always` recreates the services whose image it replaced. A registry the client cannot reach leaves the stored image alone instead of failing. (by @jochumdev and @alien43) - ic-healthd treats Incus's `instance-resumed` as a start, so `unpause` puts a service back under watch at once instead of at the next resync. (by @jochumdev) - A config or secret setting `uid` without `gid` leaves the group at 0, the way docker does, instead of taking the instance's. (by @jochumdev) - Waiting for a container's IP reports a clearer timeout, naming the likely cause and a command to check it. (by @jochumdev) ### Fixed - The image cache is keyed by architecture, so a cluster mixing architectures no longer serves one member's image to all of them. An OCI pull is pinned to the manifest digest for that architecture; a source that cannot be pinned - simplestreams, a native `incus:` remote - is checked once stored and deleted again. Arch-blind entries from an earlier version are re-fetched once and left in the cache until deleted by hand. (by @jochumdev) - `platform:` is honoured for pulled images, not only built ones, in the OCI spelling docker uses (`linux/arm/v7`), and an unsupported one is an error. `arm/v6` and `arm/v7` resolve to the right manifest, and two services wanting one image reference for different architectures is reported rather than served from whichever was configured first. (by @jochumdev) - `user:` may name its user and group (`user: "netbox:root"`), resolved against the image's own `/etc/passwd` and `/etc/group`, and so may the image's own `USER`. A name the image does not define is an error rather than a silent fall back to root; a name with no group takes that user's own group, a number with no group keeps GID 0. (by @jochumdev) - A config or secret whose target sits inside a volume is written into that volume, instead of into the instance's filesystem where the mount hid it. (by @jochumdev) - `up` no longer hangs until the start timeout on a service already reported healthy. The wait for a container's IP refreshed the shared instance state as it polled, overwriting the verdict a dependent service was waiting for, and now reads into a state of its own. Health-check dependencies also wait for ic-healthd's own checks to pass, not only for its instance to be `Running`. (by @jochumdev) - A health check could fail with `websocket: bad handshake` instead of reporting its result: the exec control socket was dialed in map order, so a fast command could finish and retire the operation first. (by @jochumdev) - `healthd up` stops at the first resource that fails, and no longer leaves a storage volume behind that every later attempt fails on with `UID mismatch`. (by @jochumdev) - An OCI image whose `CMD` is empty is no longer re-read from its registry on every run. (by @jochumdev) ## [v1.2.0] - 2026-08-14 ### Changed - Incus 7.0.1 (LTS) or 7.2 is now the minimum, checked when a command connects rather than failing somewhere further in. Older daemons have no `oci_network_config`, which every compose network attachment relies on for static addresses, gateways and `oci.dns.*`. (by @jochumdev) - The bridge the shared ic-healthd daemon attaches to is now called `icompose0` rather than `ic-healthd`. A daemon that is already running keeps its current bridge until it is recreated, and the old network is not removed for you. (by @jochumdev) - `up --no-start` now returns once the containers are created, as `--detach` does. It used to go on to follow logs from instances it never started, and the interrupt that ended that stream tore them down again. (by @jochumdev) - `up --build` now recreates the instances of the services whose image it rebuilt, so the new image is what they run. Previously the image was rebuilt but the existing instances kept the old one until `--recreate` was passed as well. A service that only consumes an image another service builds is recreated too; everything else is left alone. (by @jochumdev) - `--pull always` on an image from an OCI registry re-downloads it rather than keeping a cached copy whose digest still matches. Deciding that needed a client-side registry lookup; resolving the reference is now left to the Incus server. Native `incus:` remotes are unaffected. (by @jochumdev) ### Added - `sysctls:` on a service is mapped to `linux.sysctl.*` on the instance, applied immediately and kept across a restart. (by @alien43) - `x-incus-compose.gateway: false` on a service's network attachment allows a static `ipv4_address`/`ipv6_address` on a network that declares no CIDR. That combination is otherwise rejected, because the gateway is not known until the network exists. (by @jochumdev) ### Fixed - A NIC device carrying `nictype` (`bridged` with a `parent`, say) and no managed `network:` is accepted instead of rejected. Incus takes such a device on its own, and it is the only way to attach an instance to an unmanaged host bridge. (by @alien43) - A failed image build says what failed. The lock the build takes first reported the daemon's error unwrapped, so anything wrong with it read as a bare `not found` against the image being built; it now names the lock volume and the project it lives in, and `--debug` reports which stage the build reached. (by @jochumdev) - `name:` on a network now selects the Incus network it names, external or managed. It was documented but never read, so only `x-incus-compose.network` had any effect; an explicit `name:` now wins over that extension. (by @jochumdev) - Working on several services at once no longer races. Every worker drove one shared Incus client, whose event-listener state cannot be used from more than one goroutine; each has its own connection now. The races that sat on top of it are gone with it: two workers setting up the image lock volume at the same time, simultaneous starts resolving which ic-healthd watches the project, and a wait for an instance's addresses that trusted a lifecycle event and stalled DNS registration until the timeout when one arrived late. (by @jochumdev) - ic-healthd gives up on an Incus call that stops answering instead of leaking the goroutine waiting on it, so a health check or a restart that times out no longer costs the daemon anything. A probe abandoned mid-command also has its exec canceled, rather than leaving it running in the container. (by @jochumdev) - ic-healthd waits for the instance's operation lock to clear before retrying a write it rejected, instead of retrying on a fixed delay that could expire six times while a slow stop was still running. (by @jochumdev) - Loading a compose file reads the server's API extensions once at connect rather than once per service, network and published port. (by @jochumdev) - Waiting for an image to appear in the cache gives up after the five minutes it was meant to, and stops when the command is canceled. The retry took its delay as a starting point for exponential backoff and ignored cancellation, so ten attempts could span hours that no interrupt would end. (by @jochumdev) - A start or stop held up by another operation on the same instance no longer spends its `--timeout` on backing off. The wait for the instance lock is server-side and already correct; the retry around it doubled its delay on top, up to two minutes, which could turn contention it would have ridden out into a reported timeout. Waiting for ic-healthd to come up is likewise the three seconds it claims rather than fifteen. (by @jochumdev) - A built image now carries its environment into the instance. The image's `ENV` was dropped on the way in, so a service built from a Dockerfile came up without the `PATH`, `HOME` and `TERM` the same image pulled from a registry gets. (by @jochumdev) ## [v1.2.0-rc.3] - 2026-08-07 ### Changed - The shared ic-healthd runs in an Incus project of its own, `incus-compose`, on a bridge of its own, `ic-healthd`, with its own root disk. It took all three from the `default` project's `default` profile before, so a server whose default profile uses an unmanaged bridge - or no NIC - could not bring the shared daemon up at all. No instance or volume of ours lands in the `default` project any more. (by @jochumdev) - `--healthd-network` / `x-incus-compose.healthd.network` now applies to the shared daemon too, and a network the compose file declares is created before the daemon attaches to it. It was warned about and ignored outside project scope. Like `incus`, `workers` and `x-incus`, the first project to bring the shared daemon up supplies it. (by @jochumdev) - ic-healthd's default pool sizes are `workers: 128` and `restart-workers: 32`, up from `32` and `12`. One daemon now watches every project, so the caps are fleet-wide and the old ones queued behind a handful of slow projects. (by @jochumdev) > **Upgrading from `v1.2.0-rc.1` or `rc.2`** - those left a daemon in the > `default` project, and nothing moves it for you. Run > `incus-compose healthd down --force` **before** upgrading, or afterwards > delete the `ic-healthd` instance and volume in the `default` project and its > `ic-healthd-global` certificate. Two daemons watching the same projects > otherwise both restart the same instances. Releases before `v1.2.0-rc.1` had > no shared daemon and need nothing. ### Fixed - `up` no longer fetches the ic-healthd image when the daemon already runs the one asked for. It pulled on every run, so a tag that had gone from the registry - or a registry that was simply unreachable - failed the whole project even though the daemon was healthy and nothing needed replacing. (by @jochumdev) - Containers on a network that pins its own subnet can reach the outside again. Incus turns `ipv4.nat`/`ipv6.nat` on only for a subnet it picked itself, so a network given an explicit `ipv4.address` through `x-incus` came up without NAT and nothing on it could route out. Both now default to `true` for any non-`internal` network, matching docker; set them in `x-incus` to say otherwise. Networks that already exist keep the setting they were created with. (by @jochumdev) ## [v1.2.0-rc.2] - 2026-08-06 ### Fixed - `self-update` installs the build for the machine it runs on. It picked the first asset in the release instead - `darwin_amd64` for everybody - so on Linux and Windows it replaced the binary with one that cannot execute. The broken `self-update` is the one already installed, so 1.0.0 and 1.1.0 users have to reinstall once with `install.sh`; it works from here on. (by @jochumdev) ## [v1.2.0-rc.1] - 2026-08-06 ### Added - ic-healthd watches several projects from one event listener, and `--project` is now optional: without it it watches every project whose config matches `--project-marker`, by default `user.healthcheck.scope=global`. The flag takes a `KEY=VALUE` pair now; a bare key still means `KEY=true`. See [Health Checking](https://docs.incus-compose.org/healthd). (by @jochumdev) - `x-incus-compose.healthd` gained `scope`, `workers`, `restart-workers` and `x-incus`. `workers`/`restart-workers` size the daemon's pools; `x-incus` is Incus instance config for the sidecar, e.g. `limits.cpu`. (by @jochumdev) - `up` and `healthd up` upgrade the ic-healthd container: when the image you ask for is a newer release than the one it is running, the daemon is replaced by one built from it. The comparison is semver and forward-only, so a machine on an older incus-compose cannot downgrade a daemon shared with everyone else. Tags that are not release versions - moving tags like `latest`, and `git describe` builds - are not comparable and replace on any difference. The replacement keeps the running daemon's configuration - its endpoint, worker counts, limits and anything else set on it - so an upgrade triggered by one project no longer resets settings another supplied. A flag or compose value given to the run doing the upgrade still wins, and a limit below the sidecar's own default is raised to it. (by @jochumdev) - the `healthd` sub-commands run without a compose file, acting on the shared daemon. `incus-compose healthd up` on a bare server creates it before any project exists; `logs`, `restart`, `reload` and `down` fail with `no ic-healthd is running` when there is none instead of complaining about a missing `compose.yaml`. (by @jochumdev) - `--trace`, a level below `--debug` (which it implies), on both incus-compose and `ic-healthd run`. The daemon's per-event and per-check lines moved there, so `--debug` stays readable on a server watching many projects; `--trace` is what shows the Incus events arriving when a project is not being watched. incus-compose passes it to the sidecar as `INCUS_COMPOSE_HEALTHD_TRACE`; it has no level of its own in incus-compose yet. (by @jochumdev) - `healthd down --force` stops the shared daemon without the confirmation prompt. Without it, taking down a daemon other projects rely on asks first, and refuses outright when there is no terminal to ask on. (by @jochumdev) - `entrypoint:` is supported and follows the compose spec. `command:` on its own still appends rather than replacing. See [Compose Compatibility](https://docs.incus-compose.org/compose-compatibility#entrypoint-and-command). (by @jochumdev) - `--pull never` on `up`, `build` and `pull` never contacts a registry, for air-gapped use. `pull --policy` is honoured instead of ignored. (by @jochumdev) ### Changed - **one ic-healthd for the whole server.** `up` no longer creates a sidecar per project. It creates a single shared daemon named `ic-healthd` in the Incus `default` project and marks the project `user.healthcheck.scope=global` so the daemon picks it up. A project that already had its own sidecar has it removed, before the mark is written, so the two never watch it at once. Keep a sidecar of your own with `up --healthd-scope project` or `x-incus-compose.healthd.scope: project`; that is also the way to keep the daemon's Incus certificate restricted to one project, since the shared one is unrestricted by necessity. Whichever a project uses is stored on the project and wins over the flag and the compose file from then on, so changing it later means changing that key. Projects last brought up by an earlier version carry no scope at all and are invisible to the shared daemon, so nothing changes for them until you run `up`. See [Health Checking](https://docs.incus-compose.org/healthd). (by @jochumdev) - **health checking is opt-in.** ic-healthd watches an instance only when it carries `user.healthcheck.enabled: "true"`; a `healthcheck:` block or a restart policy alone is no longer enough. incus-compose writes it automatically. Instances created before this do not carry the key. If a project uses `healthcheck:` or a restart policy, run `up` once to have those enforced again; it adds the key in place, with no `--recreate` and no downtime. Projects that use neither need nothing, and containers keep running either way. See [Health Checking](https://docs.incus-compose.org/healthd#health-checking-is-opt-in). (by @jochumdev) - ic-healthd caps the checks and restarts it runs at once, over every project it watches, with `--workers` (32) and `--restart-workers` (12). An action with no worker free is retried rather than queued, so it never counts as a check that timed out. The sidecar is created with 2 CPUs and 256MiB instead of 1 and 50MB, and an existing one is raised to that when it is replaced. Only a project-scoped sidecar counts against an aggregate `limits.cpu`/`limits.memory`; the shared daemon lives in the Incus `default` project and counts against nothing. (by @jochumdev) - `user.healthcheck.status` is written by ic-healthd alone. incus-compose no longer stamps `starting`/`stopped` on it, so the value always says what a daemon actually saw: an instance carries no status until one reports, reports `stopped` while it is down, and `unknown` for good under `up --no-healthd`. `list` shows those as `Unknown` and `Stopped`. (by @jochumdev) - the image cache moved from the Incus `default` project to `incus-compose-cache`. Whatever earlier versions cached in `default` stays there unread; delete it by hand. (by @jochumdev) - a service with `build:` no longer rebuilds in every project - the cache is checked before the builder runs. Use `--build` to force a rebuild. (by @jochumdev) - `up` without `--detach` now matches `docker compose`: create, start, stream logs, and `down` on interrupt. (by @jochumdev) - `config --format=json` keeps the `x-incus` and `x-incus-compose` blocks, which `docker compose` drops. Parse the JSON rather than diffing it against docker's. (by @jochumdev) ### Fixed - ic-healthd reliability: stalled API calls, checker cancellation races, invalid intervals, `unless-stopped` misread as a deliberate stop, instances re-checked forever after they stopped, and state lost on an event-listener reconnect. (by @jochumdev) - concurrent `up` runs no longer fail creating the same volume, profile or network. (by @jochumdev) - `build.dockerfile` is resolved relative to `build.context`, not the working directory. (by @jochumdev) - `command:` arguments containing spaces, quotes or `$` are shell-quoted correctly instead of being re-split. (by @jochumdev) - a `configs:` or `secrets:` entry whose target already exists in the image is written instead of silently skipped. (by @jochumdev) - a static `ipv4_address`/`ipv6_address` on a network with no explicit address now fails with an explanation instead of producing a broken NIC. (by @jochumdev) - `config --format=yaml` no longer nests the document under a `project:` key. (by @jochumdev) - default storage pool detection checks the `default` profile's root device first. (by @jochumdev) - a service's `x-incus.raw.dnsmasq` lines are no longer appended twice. (by @jochumdev) - pushing directory content into a storage volume no longer closes each file twice. (by @jochumdev) ## [1.1.0] - 2026-07-31 ### Changed - `build:` now works on every platform instead of Linux/Unix-only: image building previously unpacked the built OCI image with `umoci` (gated to `unix && !darwin`, with a "not implemented" stub elsewhere), and now instead derives the minimal `config.json` needed by Incus's LXC driver (`Process.Args`/`Cwd`/`User`) straight from ` inspect`, dropping the `opencontainers/image-spec` and `opencontainers/umoci` dependencies. Builder detection also no longer shells out to ` version` to distinguish Podman from Docker; it now checks the resolved binary name, and `buildah` is tried alongside `podman`/`docker`. (by @jochumdev) - Published ports (`ports:`) create a proxy device. By default this is a userspace proxy targeting the container loopback (`nat=false`, connect `127.0.0.1`); per-port `x-incus-compose.nat: true` opts into NAT mode instead, connecting via ARP/NDP-based instance IP detection (needs Incus 7.2 or 7.0.1 LTS) or the NIC's static IP directly if one is configured (same version floor). Requesting `nat` on a server below that floor skips the port with a warning instead of silently falling back. `nat` was previously auto-enabled on Incus 7.0+; it's opt-in now because NAT mode doesn't work for host-side `localhost:` access — it routes to the instance's real address, not the loopback interface. (by @ishaan-jindal) - `ic-healthd` is now event-driven instead of poll/SIGHUP-based: it discovers instances once, then reacts to the Incus lifecycle event stream (start, stop, shutdown, delete) to keep its tracked set in sync, spawning or killing checkers for exactly the delta. A checker only probes and reports its own status; the runner alone decides whether to restart an instance. `incus-compose healthd restart` no longer needs to register a client-side reloader hook, since healthd resyncs itself from events. (by @jochumdev) - `up`'s wait for a service's own healthcheck to become healthy, and its wait on `depends_on: { condition: service_healthy }` dependencies, no longer poll Incus every 500ms. The client now opens a project-scoped Incus lifecycle event listener and blocks on a per-instance condition variable that's broadcast whenever that instance's state is refreshed from a lifecycle event (start/stop/update), cutting idle `GetInstance` API calls during `up`. (by @jochumdev) - Image caching: images built with `build:` now go through the same cache path as pulled images instead of being created directly in the project, fixing stale/duplicate builds when a cache is configured. Use `build.no_cache: true` to disable caching. `deleteCached` also no longer aborts before cleaning up the cache when the source image was already removed. (by @jochumdev) ### Removed - `x-incus-compose.nat-proxy` extension and all associated post-start device attachment machinery. Ports are now handled entirely through the standard `ports:` field. (by @ishaan-jindal) ### Added - `healthd up` now recreates the ic-healthd sidecar when the running instance's image no longer matches the configured one, instead of leaving it on the stale image until a manual `healthd down` first. `up` shares the same code path, so a plain `up` also picks up healthd image updates automatically. (by @jochumdev) - `services.{name}.configs` / top-level `configs:`: mount config files into the container, sourced from a file, inline `content`, or an environment variable. `mode` defaults to `0444` (world-readable); the writable bit is always ignored per the compose-spec, even if an explicit `mode` is set. (by @ishaan-jindal) - Well-known OCI registries (`docker.io`, `ghcr.io`, `mcr.microsoft.com`, `quay.io`, `registry.gitlab.com`) are now auto-added to the in-memory Incus CLI config when an image from that registry is used, removing the need for manual `incus remote add` steps. (by @ishaan-jindal) - Do not ignore healthd in `up --no-deps ` it allows script to wait on the service to be ready. Use `up --no-deps --no-healthd ` if you want the old behaviour. (by @jochumdev) - `x-incus-compose.healthd.external: true`: the compose-file equivalent of `up --external-healthd` / `down --external-healthd`, so a project can pin "bring your own healthd" permanently instead of passing the flag on every invocation. Combines with the flag by OR: either is enough to turn it on. (by @jochumdev) - `networks.{name}.aliases` on a service's network attachment now registers extra DNS names for its instance: each alias becomes a `cname=,` record in the network's `raw.dnsmasq`, resolving immediately instead of waiting on a DHCP lease like the existing service-name records. Works across networks shared between projects (`external: true`) without clobbering the other project's records. Limited to single-instance services, since a CNAME alias can only point at one target. (by @jochumdev) - `dns` / `dns_search` / `domainname` now map to Incus's `oci.dns.nameservers` / `oci.dns.search` / `oci.dns.domain` instance config keys, seeding the container's initial `/etc/resolv.conf`. `dns_opt` has no Incus equivalent and is not mapped. (by @jochumdev) ### Fixed - `install.sh`: fixed the checksum filename to match goreleaser's current release-artifact naming (`checksums.txt`), it was still using the old `${PROJECT_NAME}_${VERSION}_checksums.txt` pattern. (by @jochumdev) - `up --pull=always` and `pull`: the stale image was not always deleted from cache and project before re-copying, so a floating tag could keep serving the old image. Deleting the cache is now a distinct step that runs before create/refresh, and the well-known-registry hook fires on it too. (by @jochumdev) ### Internal - `.golangci.yml`: enabled a much stricter linter set, and fixed the resulting findings across the codebase. (by @jochumdev) ## [1.0.0] - 2026-07-10 The first stable release! _hooray_ ### Changed - Refactored the whole image caching process, it's now doing the same as the incus client would do and allows disabling caching by setting it to empty. - `self-update` got a `--drafts` flag and skips them by default. ## [1.0.0-rc.2] - 2026-07-08 Second release candidate cause of the breaking `user.` -> `user.label.` change below. ### Changed - Labels now have a `user.label.` prefix instead `user.` only, to not conflict with other user settings. ## [1.0.0-rc.1] - 2026-07-07 First release candidate. File pushes move to the Incus SFTP API, `command` now layers on top of the image entrypoint instead of replacing it, and `privileged` services are supported. This is the first release that should actually work on Windows and MacOS. E2E suite green. ### Added - `services.{name}.privileged: true`: run the container privileged (`security.privileged`). ### Changed - File pushes (secrets and single-file bind seeds) use the Incus SFTP API instead of the old REST file endpoint. - `command:` is appended to the image's `oci.entrypoint` as arguments instead of overwriting it, matching Docker's ENTRYPOINT/CMD semantics. - `ic-healthd` logs more detail during operations. - `down --volumes` now deletes volumes while keeping the project; it is no longer an alias for `--project`. Use `--project` to remove the whole project (and its volumes). - `list` includes the ic-healthd sidecar by default; the `--healthd` flag is replaced by `--no-healthd` to omit it. ### Fixed - `healthd up` / `healthd down` work with custom networks. - Windows and macOS builds error cleanly instead of crashing on the umoci import. ### Removed - `healthd up --recreate`; recreate the sidecar with `healthd down` followed by `healthd up`. ### Internal - CI runs slow tests with a 20m timeout and without parallelism to avoid overload; tooling paths and changelog links updated; lint fixes.
Pre-1.0 beta history (beta1 through beta.22, 2026-06-01 to 2026-07-06) ## [1.0.0-beta.22] - 2026-07-06 A real `pull` command, Docker-parity `user` handling and `exec`, plus per-service raw devices and gateway selection. E2E suite green still at ~60% coverage. ### Added - `pull` command: pre-pull service images (and the healthd sidecar) without creating anything, with `--policy`, `--ignore-buildable`, `--ignore-build-failures`, `--no-healthd`, and `--with-deps`. - `services.{name}.user`: run the container process as a numeric `UID` or `UID:GID` (mapped to `oci.uid` / `oci.gid`). - `services.{name}.x-incus-compose.devices`: attach raw Incus devices (gpu, unix-char, ...) verbatim; the required `type` key selects the device type. - `services.{name}.networks..x-incus-compose.gateway: true`: places that NIC last so Incus uses its gateway as the instance's default route. ### Changed - `exec` runs as the instance's user/group by default (matching `docker compose exec`); override with `--user` / `--group`. The command and its arguments are passed to Incus verbatim, so leading-dash flags work unescaped. - Service network attachments are ordered deterministically (they previously followed Go map iteration order). - Documentation moved to https://docs.incus-compose.org. ## [1.0.0-beta.21] - 2026-07-04 Standalone and bugfixed healthd, more x-incus reach, a native exec, and an error-severity system so recoverable problems warn instead of aborting. E2E suite green, ~60% coverage. ### Added - `x-incus` extensions now pass through on service networks, service volumes, and devices, plus direct `tmpfs` on services (same verbatim key/value passthrough as instances and networks). - Standalone `ic-healthd`: it now has its own tests and can run on its own. Env vars renamed to the `INCUS_COMPOSE_HEALTHD_*` prefix, and a `--token` flag was added. - Error-severity system: `Clone()` and `IgnoreError()` let commands demote non-fatal problems to warnings instead of hard failures. `up`/`down`/`start`/ `stop`/`restart` no longer abort on errors that don't matter. - `StackFailFast()` and `Stack.SetOptions()`. - Exported `SanitizeProjectName()`. ### Changed - `exec` uses the native `incus exec` implementation instead of the in-house MVP terminal (~250 lines removed): better TTY handling and parity with the `incus` CLI. - Overridden network names are honored for normal networks too, not just special cases. - OCI config is extracted after a build; resource dedup now keys on both `Name()` and `IncusName()`. ### Fixed - Instance volumes land on the correct storage pool. - `security.shifted` is left alone when the user has set it. - `progress.bypass()` for all stdout/stderr fixes garbled output (#37). - DNS watcher is skipped when the service name equals the Incus name; no watcher for empty service names. ## [1.0.0-beta.20] - 2026-06-30 Internal project/stack refactor plus network-readiness and healthd reliability fixes. E2E suite green, ~50% coverage. ### Added - Instances wait for the network before starting via `raw.lxc=lxc.start.delay=1`, fixing flaky startups where services came up before DNS/networking was usable. ### Changed - Reworked the ordering logic for `up`/`down`/`start`/`stop`, with and without dependencies. Deliberate asymmetry: `up`/`down` follow `depends_on` by default (`--no-deps` limits to the named service); `start`/`stop`/`restart` act on the named service only (`--with-deps` makes them follow `depends_on`). - Project no longer returns a `Stack`; the CLI now owns stack assembly, with a new helper that adds resources in priority order. - Exported `SanitizeNetworkName`. ### Fixed - DNS update retries once on an ETag mismatch (concurrent-update race). - `user.healthchecking.stopped` updates go through a cleaner path; the hacky PATCH workaround is gone. ### Removed - deb/rpm/apk packages. Releases now ship the tarball/binary and install script only. ## [1.0.0-beta.19] - 2026-06-29 Mostly CLI and healthd fixes, plus event-driven log following. ### Added - Event-driven `logs --follow`: uses the Incus events API to attach and detach log streams as instances start and stop, no longer exiting when instances go away and picking up new instances automatically (#3). ### Changed - `down --project` now deletes all resources (instances, networks, volumes, and the healthd sidecar) instead of relying on incus to do so. - `--debug` no longer shows progress bars (they interfered with debug output). - DNS watcher waits up to 5s after a dnsmasq restart before starting the next instance. ### Fixed - healthd: restart counting during the start period, instance tracking after cancellation, and the `healthd up`/`healthd down` lifecycle (#5). ### Removed - Automatic retry on client operations. - The unnecessary `--with-deps` flag from `logs`. ### Docs - Added a Terms section (#4); updated example healthchecks with `start_*` directives; immich example now waits for DNS readiness and drops tini. ## [1.0.0-beta.18] - 2026-06-28 ### Changed - **Breaking:** renamed the `--project-directory` shorthand from `-pd` to `-P`. - **Breaking:** `core.https_address` is now required (the server must be reachable over the network for image caching); the CLI warns when connecting over a unix socket. - Lowered the default `--workers` from 10 back to 4 to avoid storage IO contention on cold-cache / large-image starts. - Use the non-`v` version for the healthd image while keeping the `v` prefix for incus-compose itself. ### Fixed - Retry various client/CLI operations and tune the default timeouts; increased the delay between start/stop retries. - `up --recreate` no longer recreates networks. - `ic-healthd` no longer shows up as an orphan in `ps` output. - healthd always restarts checkers on reload so new settings take effect. - Silenced two noisy debug logs. ## [1.0.0-beta.17] - 2026-06-27 Hotfix on top of beta.16, focused on the health-check sidecar. ### Added - Opt-out of volume shifting (`security.shifted`) for cases where matching IDs inside the container don't matter. ### Fixed - `ic-healthd` now runs inside the project stack and attaches to the project's own network (the project default unless overridden via `x-incus-compose.healthd.network`), ensured just before regular instances. Network extra options are no longer lost. ## [1.0.0-beta.16] - 2026-06-26 Requires updating via the install script (version format changed to `beta.XX` for a correct semver version). ### Added - `list` now has a separate `HEALTH` column instead of appending health to `STATUS` (columns: KIND, NAME, INCUSNAME, IMAGE, STATUS, HEALTH, ADDRESSES). - Every instance reports a health value; services without a healthcheck show "Unknown" rather than a blank. - `up`/`start` wait for the healthcheck to report healthy after starting an instance that defines one (polled every 500ms, bounded by `--timeout`), making `depends_on: service_healthy` reliable. - `ic-healthd` reports its own status (healthy on start, unhealthy on shutdown) and locates the daemon instance via a `user.healthcheck.daemon` marker. - New `--healthd-incus` flag / `INCUS_COMPOSE_HEALTHD_INCUS` env var to set the API URL the sidecar connects to (empty = auto-detect the IP from the attached bridge). - New top-level `x-incus-compose.healthd` extension (`incus` API URL and `network` as `:` or a plain bridge name; both default to the project's own network and the connection's port). ### Changed - **Breaking:** compose now defaults to incus listening on all interfaces; set `INCUS_COMPOSE_HEALTHD_INCUS` to override. - `up` reconciles the service count in both directions, matching `docker compose up`. Instances above the desired count (`deploy.replicas` or `--scale`) are torn down, highest index first. A manual `--scale N` applies only to that invocation (#12). - Default `--timeout` raised from 10s to 1 minute. ### Removed - **Breaking:** the defunct `x-incus-compose.network-profile` extension (replaced by `x-incus-compose.healthd`). ## [1.0.0-beta15] - 2026-06-23 ### Added - `self-update` command: checks GitHub releases and updates the binary in place (release builds only, when the binary directory is writable). - Environment variables for all CLI flags; every global flag can now be set via `INCUS_COMPOSE_*` (e.g. `INCUS_COMPOSE_FILE`, `INCUS_COMPOSE_PROJECT_NAME`, `INCUS_COMPOSE_DEBUG`). - Configurable worker count via `--workers` / `INCUS_COMPOSE_WORKERS` (default 10). ### Fixed - `.incus.yaml` overlay loading: `docker-compose.incus.yaml` and `compose.incus.yaml` overlays were not loaded correctly (#6). - Progress display and error rendering improvements (#7). - healthd retry calculation: `retries = start_period / start_interval`. - Small client-package cleanups. ### Docs - Improved healthd documentation. ## [1.0.0-beta14] - 2026-06-22 ### Added - Network project/profile support: `x-incus-compose.network.project` and `x-incus-compose.network.profile` control which Incus project and profile healthd uses. ### Changed - **Breaking:** bind-mounts are no longer seeded by default; they now default to non-seeded (simple disk device pass-through). Set `x-incus-compose.seed: true` on the volume to restore copying files into the instance. ### Fixed - Client connection stability: fixed several data races (reused `ProtocolIncus`, `noColor` context var, random string generation). - Network profile fallback: use `devices.eth0.parent` when `devices.eth0.network` isn't available in a profile. - More robust healthd discovery (`FindHealthd`). - Switched to `errors.As()` for proper error unwrapping. ### Internal - Removed all remaining testify/suite usage; refactored `serviceToInstance()` into smaller helpers; split `project/project.go` into smaller files; removed dangling test fixtures; updated snapshots for the new bind-mount behavior. ## [1.0.0-beta13] - 2026-06-17 ### Changed - incus-compose is now part of the **lxc** organization on GitHub. - The `main` branch entered a feature freeze ahead of 1.0.0. ### Fixed - Do not assume the availability of `incusbr0`. ## [1.0.0-beta12] - 2026-06-13 ### Added - `--image-cache` global flag (`INCUS_COMPOSE_IMAGE_CACHE`) to point the image cache at a different Incus project (default: `default`). - `--rmi` / `--images` on `down` to remove project images on teardown, matching docker compose behaviour. - Extra storage volume config support via `x-incus-compose`. ### Fixed - `--with-deps` scoping: `up`/`down` follow `depends_on` automatically; all other commands require an explicit `--with-deps`. - Healthd is now skipped when no services require it. - Build image name corrected to `localhost/`. ## [1.0.0-beta11] - 2026-06-11 Moved to 1.0.0 from 0.0.1 (1.0.0 will be the final version). Mostly a health/dependency/lifecycle hardening release. ### Added - Healthy-dependency support: `up` wires `service_healthy` dependencies into start ordering/waiting; new `--dependency-timeout` flag. - Healthd expanded and rewritten: now runs when services use restart policies or are depended on via `service_healthy`, not only for an explicit healthcheck. Supports `start_period`, `start_interval`, `interval`, `timeout`, and `retries`; health state standardized via `user.healthcheck.status`. - `shm_size` maps to a `/dev/shm` tmpfs device. - `container_name` support: used as the Incus instance name; scaled services become `container_name-1`, `container_name-2`, etc. - Added `examples/many-dependencies` and `examples/wikijs`; moved `test/fixtures/immich` to `examples/immich`; timestamped test logs under `test/logs/`; added `just test-slow`. ### Changed - **Breaking:** removed the direct Incus URL env vars from the documented/runtime connection flow (`INCUS_COMPOSE_URL`, `INCUS_COMPOSE_CERT`, `INCUS_COMPOSE_KEY`). Use Incus CLI remotes instead (`--remote`, `INCUS_REMOTE`, or the default remote). - **Breaking:** timeout flags changed from integer seconds to Go duration values internally (`up`, `down`, `start`, `stop`, `restart`, healthd paths); use explicit durations like `--timeout 10s`. - **Breaking:** healthd sidecar name changed to `{project}-ic-healthd`; scripts expecting plain `ic-healthd` need updating. - Improved CLI progress: healthd commands participate, progress moved to stdout and is less likely to corrupt logs. - DNS watcher rewritten for concurrent per-action updates; instance IP handling refactored to support multiple interfaces/IP sets. ### Fixed - `exec` now targets the requested service instead of possibly choosing the wrong instance from the stack. - `list`/`ps` output is sorted for deterministic output (#46). - Progress no longer overwrites logs (#43), plus several writer/stdout/stderr fixes. - Healthd stability: graceful deletion/reload, checker optimization, and the normal checker now starts correctly after the start-period checker succeeds. - Instance lifecycle: fixes around already-running instances and recreate; Incus timeout `0` means "do not wait", so the internal default now maps to `-1` where needed. - Storage volumes: better shifted-volume validation, delayed from `Ensure()` to `Start()` where runtime UID/GID data is available. - Image properties are copied correctly instead of sharing/mutating state. - Several fixes around deleting project, external, and managed/dangling networks. - Uses `errors.Is(err, ErrNotFound)` in more places; better debug logging and wrapping. ## [0.0.1-beta10] - 2026-06-08 Making Compose workflows more complete: local image builds, better progress feedback, and improved healthcheck restart behavior. ### Added - Compose `build:` support. New commands and flags: `incus-compose build`, `incus-compose build `, `up --build`, `up --no-build`. See `docs/build.md`. - Progress output for all operations in the CLI. ### Changed - **Breaking:** no more Windows client support (a consequence of adding build; file an issue if you need it). - **Breaking:** storage volume names changed to `vol-{name}` (was `{project}-{name}`), hashed if longer than 59 chars. Existing volumes are not migrated automatically. - `ic-healthd` restart backoff (5s -> 10s -> 20s -> 40s -> 60s max) avoids tight restart loops for unhealthy services. - `restart: unless-stopped` handled more correctly: `stop` marks `user.stopped=true`, `start` clears it, and `ic-healthd` skips automatic restarts while a service was intentionally stopped. ### Fixed - Bind-mounted directories now resolve through the actual Incus storage volume name. - Copied files/directories use more normal permissions: `0644` / `0755`. - Fixed a UID/GID copy bug. ## [0.0.1-beta9] - 2026-06-07 Major image-handling rework, plus volume/bind-mount and healthd changes. ### Changed - OCI config is extracted at download time: UID, GID, entrypoint, and cwd are read from a temporary stopped container right after download and stored as image properties (`oci.uid`, `oci.gid`, etc.) via `UpdateImage`; later runs read them back from properties with no extra container. - Two-stage image cache restored (source -> cache project -> instance project), along with `docs/architecture/client/image.md`. - Deferred source/cache detection: `GetImageServer` and `EnsureProject` moved from `newImage()` into `Ensure()`, so no network connections happen during the configuration phase (fixing CI slowness). - Resource deduplication: `ResourceStore.Get` now compares by `IncusName()`, so normalized references (`docker.io/nginx:alpine` vs `docker.io/library/nginx:alpine`) return the same object, preventing duplicate alias races. - Bind-mount files are pushed post-start via `InstanceFile`; bind-mount directories become storage volumes with `HostPath` seeding on create. `PostDevices` and `ActionPostEnsure` are removed; volumes live in `Devices` and are ensured before `CreateInstanceFromImage`. - Healthd resource removed: the `client.Healthd` wrapper is replaced by `healthdUp`/`healthdDown` helpers in `cmd/incus-compose/healthd.go`. `KindHealthd`, `HealthdConfig`, and `resource_healthd.go` are deleted. - Healthd instance is prefixed with the project name to prevent cross-project collisions. ### Fixed - Skip token registration when a cert already exists (prevents repeated re-registration on `ic-healthd` restart). - `down` network-listing failure during `--project` delete is demoted to a warning; a nil-check prevents a panic when listing fails. ### Internal - justfile version tags gained `--long --dirty` for healthd builds. ### Breaking - Removed the project name from storage volume names (manual migration required; note beta10 renamed volumes again to `vol-{name}`). ## [0.0.1-beta8] - 2026-06-06 ### Added - `list --healthd`: opt-in flag to include the `ic-healthd` sidecar in list output. - `up --detach|-d`: detach after starting services; logs are printed if not detached (#38). - `incus-compose incus proxy`: pass-through command (`incus --project={name} `) (#37). - Image name in `ps`/`list`: the IMAGE column is populated for all instances, including the healthd sidecar, without requiring Image resources in read-only stacks (stored as `user.image_alias` at creation, resolved from `volatile.base_image` as a fallback). - healthd resource limits: the sidecar is capped at 1 CPU and 50 MB RAM by default (required for project-wide limits). - `ic-healthd` now compiles in and prints its version. - `oci-registry-cache` promoted to a standalone helper project. ### Changed - `healthd restart` works as intended: kill a service and healthd brings it back. - **Breaking:** `--no-pull` replaced by `--pull` (string flag) for docker compose compatibility. ### Fixed - `down` no longer deletes externally-managed networks. - healthd: fixed API endpoint resolution when connecting to the Incus socket (#39). - Various fixes for projects that attach to pre-existing external networks. ## [0.0.1-beta7] - 2026-06-05 ### Added - `incus-compose healthd` subcommand group for direct sidecar management: `logs` (stream), `reload` (reload health-check config), `restart`, `up` (recreate, `--recreate` supported), and `down`. - External network name override via `x-incus-compose.network`: networks can declare their real Incus name independently of the compose key. Name resolution uses a 4-candidate probe (raw and sanitized, for both the override and the compose name) and locks in the first match. ### Fixed - serviceName truncation regression: hyphenated service names (e.g. `my-service`) were incorrectly stripped. Only trailing `-{n}` integer suffixes (the scaled instance convention) are now removed. - `ic-healthd` now appears in `list`/`ps` output. - Hardcoded default storage pool: `ic-healthd` resources now use the client's configured `DefaultStoragePool` instead of always `"default"`. - `up --recreate` on a healthd container no longer loses `--incus`/`--project` OCI entrypoint flags; `ResourceStore.Remove()` is now called on every `Delete()`. ## [0.0.1-beta5] / [0.0.1-beta6] - 2026-06-04 ### Added - Automatic loading of a `compose.incus.yaml` override file when present next to the main Compose file, keeping upstream Docker Compose files unchanged while adding Incus-specific configuration in a separate file. ### Changed - The `ic-healthd` image now uses `busybox:glibc` instead of `scratch`. ## [0.0.1-beta4] - 2026-06-04 ### Added - `x-incus` options: Compose services can pass raw Incus instance config directly through to Incus (memory/CPU limits, nesting, security flags, etc.). - Automatic loading of the default incus profile. - Project-wide `x-incus-compose.network-profile` support (disables a per-project default network/bridge). - healthd reload on service changes. ### Fixed - Network creation race that could cause dnsmasq failures in CI (avoids immediately updating a newly-created network before the old dnsmasq released its socket). - `down` now deletes compose-managed networks when the project is brought down, fixing dangling networks. - `up --no-pull` is now respected correctly (also ~2x faster test runs, 3min from 6min). ### Testing - Test coverage +17% (35% -> 52%). ## [0.0.1-beta3] - 2026-06-03 ### Added - Kernel-mode NAT proxy for port proxies via `x-incus.nat-proxy` (#30). - DHCP ranges and static IPv4 / IPv6 addresses on network creation. ### Fixed - Healthd status in `ps`/`list`. - Scaling now prunes dangling instances when `up --scale` lowers the count (#34). - `logs` omits old logs then follows when `--follow` is set. - Small fixes to keep CI green. ## [0.0.1-beta1] - 2026-06-01 Initial public beta. A ground-up Docker Compose workflow for Incus, inspired by Brian Ketelsen's proof-of-concept. ### Added - Familiar commands: `up`, `down`, `start`, `stop`, `restart`, `list` (and `ps`), `logs`, `exec`, `config`, plus `build`, `healthd`, `incus` (pass-through), and `self-update`. - Compose project parsing via compose-go, with automatic `compose.incus.yaml` overrides and `x-incus` / `x-incus-compose` extensions for raw Incus options. - Native OCI image pulling from docker.io, ghcr.io, and other registries. - Two-stage image cache in a dedicated Incus project (survives `down`/`up`, avoids registry rate limits). - Local image building via Podman/Docker. - Bridge networks with automatic name sanitization. - Static IPv4/IPv6 addresses with automatic DHCP ranges. - Port forwarding via proxy devices or kernel NAT mode. - Storage volumes with UID/GID shifting; bind mounts (pass-through by default, optional seeding). - Health checks, restart policies, and `depends_on: service_healthy` ordering via the `ic-healthd` sidecar. - Service scaling with `up --scale` and orphan pruning. - Incus project isolation. - Resource limits and other advanced compose features (`shm_size`, `container_name`, etc.). - Configuration via `INCUS_COMPOSE_*` environment variables for every flag, with a configurable parallel worker count.