Builds
Build support requires
podmanordockeron the machine running incus-compose.
incus-compose does not implement a builder itself and does not use the Buildah Go library. It shells out to a local container builder, then imports the built rootfs into Incus as an image.
Builder selection:
INCUS_COMPOSE_BUILDER, when setbuildah, when found inPATHpodman, when found inPATHdocker, when found inPATH
Examples:
INCUS_COMPOSE_BUILDER=podman incus-compose build
INCUS_COMPOSE_BUILDER=docker incus-compose up --buildIf no builder is found, build-configured services fail with an error.
Basic usage#
Build all services that define build::
incus-compose buildBuild selected services:
incus-compose build web workerStart services, building missing build-configured images as needed:
incus-compose upForce rebuild before starting:
incus-compose up --buildRequire built images to already exist:
incus-compose up --no-buildCompose examples#
Short syntax:
services:
web:
build: .Object syntax with an explicit image name:
services:
web:
image: localhost/web:latest
build:
context: .
dockerfile: ContainerfileWhen image: is omitted, incus-compose uses a local image name based on the project and service:
localhost/<project>-<service>Supported build options#
| Option | Support |
|---|---|
context |
Build context directory. Relative paths are resolved by compose-go. |
dockerfile |
Alternate Dockerfile or Containerfile path, resolved relative to context (absolute paths are used as given). |
dockerfile_inline |
Inline Dockerfile content. incus-compose writes it to a temporary file before invoking the builder. |
args |
Build arguments, passed as --build-arg KEY=VALUE. Args without values are ignored. |
no_cache |
Passed as --no-cache to the builder; also skips the shared image cache for this build. |
pull |
Passed as --pull. |
target |
Multi-stage build target, passed as --target. |
platforms |
A single platform is supported. Multiple platforms are rejected. |
service platform |
Used as the build platform when build.platforms is not set. |
Image Caching#
By default, a built image is imported into the shared image-cache project
first (the incus-compose-cache project, or whatever --image-cache / INCUS_COMPOSE_IMAGE_CACHE
points at) and then copied from there into the compose project, the same
path pulled images take.
The cache is checked before the builder runs. If it already holds the
image's alias, nothing is built and nothing is pulled - the image is copied
straight from the cache into your project. So the first up anywhere builds,
and every project after that copies.
That is what makes "build once, use many" work with build: left in place, and
it is also what lets a machine that cannot build at all - no podman, docker
or buildah, which is common on Windows and macOS - consume an image someone
else built, as long as it is in the cache.
Since: v1.2.0-rc.2
The cache key is the image name#
A built image is stored in the cache under its Incus alias, which comes from the service's image name and nothing else:
| Compose | Cache alias |
|---|---|
image: ghcr.io/me/app:v1 |
ghcr.io/me/app:v1 |
image: myapp:latest |
docker.io/library/myapp:latest |
no image:, service web |
local/web:latest |
Nothing else feeds the key - not the project name, not the build context, not the Dockerfile. Two builds that resolve to the same image name are the same cache entry, whichever project or compose file they came from, and the last build to finish wins for all of them.
So the image name is the knob: set image: explicitly on every service that
builds, and give services that build different content different names. The
localhost/<service> fallback has no project prefix, so relying on it means
two projects that both have a service called web share one entry.
Because a cache hit skips the builder entirely, editing your Dockerfile or build context does not trigger a rebuild on its own - the image name is unchanged, so the cached image still matches. Use
--buildto force one, or bump the tag inimage:. This mirrorsdocker compose, where an existing image is reused until you pass--build.
Set no_cache: true on the service's build: block to skip the shared
cache and import straight into the project instead. The service then rebuilds
in every project, which is also how you avoid sharing a cache entry with a
same-named build elsewhere:
services:
web:
build:
context: .
no_cache: trueWith no cache configured at all (--image-cache ""), every build imports
directly into the project, same as no_cache: true.
Since: v1.1.0
Reusing a built image across projects#
Nothing special is needed. Keep the build: block where it is, give the
service an explicit image: name, and every project that uses that name gets
the cached image:
services:
myapp:
image: ghcr.io/example/myapp:v1
build:
context: .The first up builds and populates the cache. Every later up - same project
or another one, same machine or another one against the same Incus - finds the
alias and copies it. The image name is the whole contract.
A consumer that only wants to use the image can drop the build: block
entirely:
services:
web:
image: ghcr.io/example/myapp:v1Both forms hit the same cache entry. Because a machine only builds on a cache miss, a client with no local
buildah/podman/docker - common on Windows and macOS - can run either form
as long as someone has seeded the cache.
Rebuild under a new tag (:v2) when the content changes rather than
overwriting an existing one. Consumers already holding a project copy of :v1
will not pick up an in-place replacement, and --build only forces a rebuild
for whoever runs it.
Since: v1.1.0
Platform handling#
Built images must match an architecture supported by the target Incus server.
incus-compose asks Incus for its supported server architectures and uses the first one as the default build target.
Supported architecture mappings include:
| Incus architecture | Builder platform |
|---|---|
x86_64 |
linux/amd64 |
i686 |
linux/386 |
aarch64 |
linux/arm64 |
armv7, armv7l |
linux/arm/v7 |
armv6, armv6l |
linux/arm/v6 |
ppc64le |
linux/ppc64le |
s390x |
linux/s390x |
riscv64 |
linux/riscv64 |
If a service requests a platform that Incus does not report as supported, the build fails before invoking the builder.
up build behavior#
For build-configured services, up builds only when the image is missing from
both the compose project and the shared image cache.
| Command | Behavior |
|---|---|
incus-compose up |
Build only on a cache miss. Copy from the cache when the alias is already there. |
incus-compose up --build |
Force rebuild, replacing the cached image, and recreate the instances that use it. |
incus-compose up --no-build |
Never build. Fail if a required built image is missing. |
In practice: the first up anywhere builds, and every up after that - in the
same project or a different one - copies from the cache. --build is how you
pick up changes to your Dockerfile or context.
What --build recreates#
An instance is created from an image, so replacing the image leaves the running
instance on the old one. --build therefore deletes and recreates the instances
of every service whose image it rebuilt - including a service that only
consumes an image another service builds, since the rebuild replaces its image
just the same. Nothing else is touched: services without a built image keep running, and so do
volumes, networks and the ic-healthd sidecar. Naming services (up --build app)
narrows it further, to the built services in that scope. --recreate is the
bigger hammer, recreating the whole project whether it was built or not.
Changed in v1.2.0: --build used to rebuild the image and leave the instances
on the old one until --recreate was passed too.
Unsupported build options#
The following Compose build options are currently not implemented:
additional_contexts,cache_from,cache_to,entitlements,extra_hosts,isolation,labels,network,privileged,provenance,sbom,secrets,shm_size,ssh,tags,ulimits
tags are intentionally ignored for now. incus-compose imports the built artifact into Incus and uses the Incus image alias needed by the project; extra Docker-style tags do not affect runtime behavior.
See Also#
- Compose Compatibility - overall feature support
- Getting Started - first project walkthrough