| Tested on | Red Hat Enterprise Linux 10.2 (Coughlan) |
|---|---|
| Package | podman-5.8.2-5.el10_2.x86_64 |
| Applies to | Any Linux host with Podman 5.2+ for .build and .image; Podman 5.7+ for experimental .artifact |
| Privilege | sudo for rootful examples on the lab host |
| Scope | Quadlet .build, .image, and .artifact preparation units — build context, ImageTag, referencing from .container, pull Policy=, image-backed volumes, experimental artifact pulls, oneshot service semantics, and version gates. Assumes Quadlet basics. Does not teach Containerfiles, podman build, generic image pulling, or full OCI artifact workflows. |
| Related guides | Podman Quadlet container file |
Before a .container service starts, something has to place the image or artifact on the host. Quadlet models that prep work as oneshot units: .build runs podman build, .image runs podman image pull, and .artifact runs podman artifact pull. Downstream units reference the Quadlet filename (web.build, base.image) and inherit systemd ordering automatically where the generator understands the suffix.
I tested on Podman 5.8.2. Confirm your build supports the unit types you need:
podman --versionSample output:
podman version 5.8.2| Unit type | Minimum Podman | Status on 5.8.2 |
|---|---|---|
.build |
5.2 | Stable |
.image |
5.x (Quadlet) | Stable |
.artifact |
5.7 | Experimental |
Why these Quadlet types exist
All three follow the same prep-then-run pattern:
.build → podman build → local image tag
.image → podman pull → cached OCI image
.artifact → podman artifact pull → local artifact storeThen a workload unit consumes the result:
prepared resource
↓
.container / .volume / other QuadletEach file becomes a *-build.service, *-image.service, or *-artifact.service oneshot that runs before dependents start.
Quadlet .build
A .build unit describes a podman build that tags an image on the host. The lab build lives beside its Containerfile under /etc/containers/systemd/:
Containerfile:
FROM registry.access.redhat.com/ubi9/ubi-minimal
RUN echo 'quadlet build ok' > /build-marker.txt
CMD ["sleep", "3600"]web.build:
[Build]
ImageTag=localhost/quadlet-web:latest
File=Containerfile
SetWorkingDirectory=unitReload systemd after adding the file:
sudo systemctl daemon-reloadRun the generated build service:
sudo systemctl start web-build.serviceThe journal shows a successful tag when the build completes. Confirm the image exists locally:
sudo podman images localhost/quadlet-web:latest --format 'table {{.Repository}}\t{{.Tag}}\t{{.ID}}'Sample output:
REPOSITORY TAG IMAGE ID
localhost/quadlet-web latest e2b4c77c0c67.build required fields
A minimal .build unit requires ImageTag= plus a build context supplied by either File= or SetWorkingDirectory=. In the common local-layout example below, using both File=Containerfile and SetWorkingDirectory=unit makes the Containerfile and its relative COPY sources explicit.
ImageTag=— the tagpodman build --tagreceives (for examplelocalhost/quadlet-web:latest)File=— Containerfile path relative to the working directorySetWorkingDirectory=— wherepodman buildruns soCOPYandADDresolve correctly
Without ImageTag= plus File= or SetWorkingDirectory=, the generator cannot produce a usable web-build.service.
Build context with SetWorkingDirectory=
podman build needs a directory that contains the Containerfile and any files it references. SetWorkingDirectory=unit tells Quadlet to use the directory that holds the .build file — in this lab, /etc/containers/systemd/.
The generated unit sets WorkingDirectory and passes that path as the build context:
systemctl cat web-build.serviceRelevant excerpt:
WorkingDirectory=/etc/containers/systemd
ExecStart=/usr/bin/podman build --tag localhost/quadlet-web:latest --file Containerfile /etc/containers/systemdSetWorkingDirectory=file uses the directory containing the File= path when the Containerfile lives in a subdirectory. SetWorkingDirectory= can also accept a Git URL for remote contexts — see podman-build.unit(5) when you outgrow a local directory layout.
Reference a .build from .container
Point Image= at the Quadlet source file, not only the tagged name:
[Container]
Image=web.build
ContainerName=quadlet-web-runAfter daemon-reload, the generated container unit pulls in the build service:
systemctl cat web.serviceRelevant excerpt:
Requires=web-build.service
After=web-build.service
ExecStart=/usr/bin/podman run ... localhost/quadlet-web:latestStart the container service on the lab host:
sudo systemctl start web.serviceVerify the running container uses the built image:
sudo podman ps --filter name=quadlet-web-run --format '{{.Names}} {{.Image}}'Sample output:
quadlet-web-run localhost/quadlet-web:latestThat automatic Requires= / After= wiring is the main reason to use .build instead of a manual ExecStartPre=/usr/bin/podman build … in [Service].
Build caching
.build is a oneshot service — it runs podman build each time you start web-build.service. Podman still uses its layer cache when nothing changed. A second start on the lab host finished in about a second and reused the existing image ID instead of re-running every RUN step.
Cache invalidation rules belong in Build images with podman build. Here the takeaway is that repeated systemctl start web-build.service is not a full unconditional rebuild unless your Containerfile or context changed.
Build secrets and other useful options
Quadlet exposes high-value [Build] keys without turning this page into a podman build reference:
| Key | Role |
|---|---|
Secret= |
BuildKit secret mount (id=, src=, env=, type=) |
Pull= |
Base image pull policy during build |
Target= |
Multi-stage build stage name |
Network= |
Network mode for the build |
Volume= |
Additional build volumes |
Example with a file-based secret:
[Build]
ImageTag=localhost/quadlet-web:latest
File=Containerfile
SetWorkingDirectory=unit
Secret=id=token,src=token.txtPlace token.txt in the build context directory. Full flag lists live in podman-build.unit(5).
Quadlet .image
A .image unit ensures an OCI image exists locally before dependents start:
[Image]
Image=registry.access.redhat.com/ubi9/ubi-minimal:latest
Policy=missingReload and start the pull service:
sudo systemctl daemon-reloadPull the base image with the generated oneshot service:
sudo systemctl start base-image.serviceThe generated command shows how Policy= maps to the CLI:
systemctl cat base-image.serviceRelevant excerpt:
ExecStart=/usr/bin/podman image pull --policy missing registry.access.redhat.com/ubi9/ubi-minimal:latestReference the unit from a container:
[Container]
Image=base.imageOr from an image-backed volume (see below).
Image pull policy
Policy= on a .image unit controls the oneshot pull service. Podman 5.8.2 supports:
| Policy | Behavior |
|---|---|
always |
Always pull; fail if the pull fails (default) |
missing |
Pull only when the image is absent locally |
never |
Use local image only; fail if missing |
newer |
Pull when the registry digest differs from local storage |
Policy=missing suits base images you mirror once per node. Policy=newer suits tags you want refreshed when the registry moves.
This is not AutoUpdate= on a .container file. Policy= runs when the image unit starts; AutoUpdate= governs a separate runtime update path for long-running services. Keep preparation and runtime refresh separate in your design.
Fully qualified image references
Use registry-qualified names in .image units:
registry.access.redhat.com/ubi9/ubi-minimal:latestShort names like ubi-minimal:latest depend on registries.conf search paths and can resolve differently across hosts. Fully qualified references make pull behavior predictable. Registry login and TLS belong in dedicated guides — wire AuthFile= or TLSVerify= only when your environment requires them.
Use .image with .volume
Image-backed volumes can reference a .image unit so the base layer exists before volume creation:
base.image:
[Image]
Image=registry.access.redhat.com/ubi9/ubi-minimal:latest
Policy=missingimgdata.volume:
[Volume]
VolumeName=img-data
Driver=image
Image=base.imageStart the volume service:
sudo systemctl start imgdata-volume.serviceQuadlet adds the image dependency:
systemctl cat imgdata-volume.serviceRelevant excerpt:
Requires=base-image.service
After=base-image.service
ExecStart=/usr/bin/podman volume create --ignore --driver image --opt image=registry.access.redhat.com/ubi9/ubi-minimal:latest img-dataSee Podman Quadlet volume and network for volume naming and mount references.
Quadlet .artifact
An .artifact unit runs podman artifact pull so non-image OCI content exists in the local artifact store before a workload needs it:
[Artifact]
Artifact=localhost:5000/quadlet-config:v1
TLSVerify=false
Retry=3
RetryDelay=2sThe lab pushed quadlet-config:v1 to a local registry on port 5000. Production artifacts should use a fully qualified registry name such as quay.io/example/config:v1 with normal TLS.
Reload and start:
sudo systemctl daemon-reloadPull the artifact into local storage:
sudo systemctl start config-artifact.serviceOn success the service is active (exited):
systemctl show config-artifact.service -p Result,ExecMainStatus,ActiveState --no-pagerSample output:
Result=success
ExecMainStatus=0
ActiveState=activeList artifacts in local storage:
podman artifact lsSample output:
REPOSITORY TAG DIGEST CREATED SIZE
localhost:5000/quadlet-config v1 a03ee86942dc 19 seconds ago 14BWhat is an OCI artifact here?
An OCI artifact packages files distributed through OCI registries — configuration bundles, policy data, or other non-container payloads you might otherwise bake into an image. Podman stores them separately from container images in the artifact store. This page only covers the Quadlet pull unit; extraction, mounts, and registry workflows belong in Manage OCI artifacts with podman artifact.
Artifact registry options
Useful [Artifact] keys map to podman artifact pull flags:
| Key | Purpose |
|---|---|
AuthFile= |
Registry credentials file |
TLSVerify=false |
Skip TLS verification (lab registries only) |
Retry= |
Pull retry count |
RetryDelay= |
Delay between retries |
Do not duplicate registry login or certificate troubleshooting here — configure auth on the host, then reference AuthFile= when the default path is wrong.
Generated service type and oneshot status
.build, .image, and .artifact units are oneshot preparation services. Depending on the generated RemainAfterExit= setting, a successful unit may appear as inactive (dead) or active (exited). Neither state alone indicates failure; check Result and ExecMainStatus.
That does not mean the build or pull failed. Check:
systemctl show <unit> -p Result,ExecMainStatusjournalctl -u <unit>podman images,podman artifact ls, or the resource the unit prepared
A failed pull shows Result=exit-code or failed with a non-zero ExecMainStatus — as with the lab's first localhost/quadlet-config:v1 attempt against HTTPS localhost without a registry.
Complete dependency example
These four files model a stack that prepares base image, application image, and config artifact before the container runs:
base.image → pre-pull UBI base
web.build → build application image (explicitly ordered after base-image.service)
config.artifact → pull config artifact (experimental)
web.container → run service (Image=web.build)Image=web.build automatically makes web.service depend on web-build.service, but Quadlet cannot infer that a FROM line inside the Containerfile corresponds to base.image. Wire that ordering explicitly on web.build:
[Unit]
Requires=base-image.service
After=base-image.service
[Build]
ImageTag=localhost/quadlet-web:latest
File=Containerfile
SetWorkingDirectory=unit
Pull=neverPull=never keeps podman build from independently re-pulling the UBI base that base.image already placed locally.
Add explicit [Unit] Requires=config-artifact.service only when your container truly needs the artifact present before start — Quadlet does not infer artifact dependencies unless you reference the .artifact file from a supported key on another unit.
Start only the container after all unit files are in place:
sudo systemctl daemon-reloadStart the application container after prep units are defined:
sudo systemctl start web.serviceOn the lab host, web.service and web-build.service were active after start. With Requires=base-image.service on web.build, starting web.service also runs base-image.service first. Wire artifact ordering manually when the application reads pulled artifact content at startup.
Troubleshooting
| Symptom | Likely cause | Fix |
|---|---|---|
Build fails with missing COPY source |
Wrong SetWorkingDirectory= or context path |
Use SetWorkingDirectory=unit when the Containerfile sits next to the .build file; verify files exist in that directory |
web-build.service inactive (dead) or active (exited) |
Normal oneshot success | Check Result=success and podman images for ImageTag= |
| Container starts before image exists | Referenced raw image name instead of .image / .build |
Set Image=base.image or Image=web.build so Quadlet adds Requires= |
.image pull every boot |
Policy=always (default) |
Set Policy=missing or Policy=newer for cached images |
| Artifact unit fails TLS | Registry cert or localhost HTTPS mismatch |
Use a fully qualified registry host; set TLSVerify=false only for known-insecure lab registries |
config-artifact.service failed |
Short artifact name or missing registry | Use fully qualified Artifact= reference; confirm podman artifact pull works manually first |
References
- podman-build.unit(5) — Quadlet
.builddirectives - podman-image.unit(5) — Quadlet
.imageandPolicy= - podman-systemd.unit(5) — experimental
.artifactunits - podman build — CLI behind
[Build]
Summary
Quadlet .build, .image, and .artifact files front-load preparation as oneshot systemd services. A .build unit tags a local image from a Containerfile context — SetWorkingDirectory=unit keeps COPY paths aligned with the .build file location. Reference it from a container with Image=web.build and Quadlet adds Requires=web-build.service automatically.
.image units pull registry images with Policy= values (always, missing, never, newer) that are separate from container AutoUpdate=. Use fully qualified image names and reference base.image from containers or image-backed volumes when you need pull ordering without hand-written dependencies.
.artifact units are experimental on Podman 5.7+. They run podman artifact pull into the local artifact store; treat registry names, TLS, and retries explicitly. After success, preparation units may show inactive (dead) or active (exited) depending on RemainAfterExit= — read Result, journals, and podman images or podman artifact ls instead of expecting a continuously active service.

