Universal Blue and Custom Silverblue Images

What They Are and How to Use Them

Universal Blue is a minimal Fedora Atomic Desktop base, while Custom Silverblue images are user-modified variants configured via Podman or system drop-ins.

Story / scenario opener

You need a Fedora Atomic Desktop with a specific set of tools baked in, or you want to test a configuration change without touching your host system. You tried installing packages directly on Silverblue and got blocked by the immutable filesystem. You found Universal Blue and Custom Silverblue images, but the documentation jumps straight to YAML files and build scripts without explaining the relationship between the base image, the container runtime, and your host. You need to know what these images are, how they differ from the official release, and how to use them safely.

What's actually happening

Fedora Atomic Desktops manage the operating system as a single immutable tree using rpm-ostree. The OS is not a collection of individual packages you install one by one. It is a single commit that you update by pulling a new image. This model guarantees consistency. You can roll back to a previous state instantly if an update breaks something.

Universal Blue is a project that provides a minimal, composable base image for this model. The official Fedora Silverblue release includes GNOME, standard desktop packages, and a curated set of utilities. Universal Blue strips all of that out. It leaves you with the core OS, the boot loader, and the rpm-ostree tooling. You use this base to add exactly what you need.

Custom Silverblue images are the result of layering packages, configuration files, or container images on top of that base. Think of Universal Blue as a car chassis. The official Silverblue release is a finished sedan with a standard interior. A Custom Silverblue image is a vehicle you build from the chassis, adding the engine, seats, and dashboard you prefer. The system treats the OS as a container image. You update by pulling a new image, not by installing individual packages.

This approach solves the reproducibility problem. When you build a custom image, every machine that pulls that image gets the exact same set of packages and configurations. There is no drift over time. You can test the image in a container before deploying it to a host.

The fix or how-to

Here is how to pull and inspect a Universal Blue base image to verify its contents before building a custom variant.

podman pull quay.io/fedora/fedora-silverblue:latest
# Pull the official base image from the Fedora registry.
# This image contains the core OS packages without a desktop environment.
podman run --rm -it quay.io/fedora/fedora-silverblue:latest rpm-ostree status
# Run a temporary container to inspect the OSTree commit metadata.
# --rm removes the container immediately after execution.
# -it allocates a terminal for interactive inspection if needed.

The output shows the commit ID, the repository URL, and the list of packages in the base tree. Note the absence of gnome-shell or firefox. This is the minimal base. You can now build a custom image on top of this.

Here is how to create a Containerfile to build a custom Silverblue image with specific packages and configuration.

FROM quay.io/fedora/fedora-silverblue:latest
# Start from the Universal Blue base image.
# This ensures you inherit the immutable OS tree and security updates.
RUN dnf install -y git vim tmux && dnf clean all
# Install packages into the image layer.
# dnf clean all reduces the final image size by removing metadata.
# Packages installed here become part of the immutable tree.
COPY custom-config.toml /etc/myapp/config.toml
# Add a configuration file to the image.
# Files added here are immutable and cannot be edited on the host.
# Use rpm-ostree kargs or overlayfs for runtime changes.

Build the image using podman build. Tag it with a meaningful name and push it to a registry if you want to share it.

podman build -t my-custom-silverblue:v1 .
# Build the custom image from the Containerfile in the current directory.
# Tag it with a version number to track changes.
podman push my-custom-silverblue:v1 quay.io/username/my-custom-silverblue:v1
# Push the image to a registry for distribution.
# Replace username with your registry namespace.

Here is how to configure the container runtime to use a custom registry or add default environment variables for your custom images.

[containers]
# Set the default pull policy to "missing" so podman only pulls if the image is not local.
# This speeds up development when you have already cached the base image.
pull_policy = "missing"

[engine]
# Add a search path for local registries if you are hosting custom images internally.
# Replace example.com with your registry hostname.
registries = ["docker.io", "quay.io", "example.com"]

[containers.env]
# Add environment variables that apply to all containers started by podman.
# This is useful for setting proxy settings or custom tool paths.
HTTP_PROXY = "http://proxy.example.com:8080"

Save this configuration in /etc/containers/containers.conf.d/custom.conf. Files in /etc/ are user-modified. Files in /usr/lib/ ship with the package. Edit /etc/. Never edit /usr/lib/. The configuration system merges drop-ins from /etc/containers/containers.conf.d/ with the default configuration.

Run podman system reset only if you are troubleshooting a broken state. This removes all containers, images, and volumes. Back up your data first.

Run podman images to verify the digest matches your build. Trust the hash, not the tag.

Verify it worked

Run rpm-ostree status on the host or podman inspect on the container. Look for the Deployments list or the Config section. The commit ID should match the image you pulled. If the layers are missing, the image did not build correctly.

rpm-ostree status
# Check the current deployment and available rollback options.
# The output lists the commit ID and the packages in the active tree.

If you installed a custom image as the host OS, the output shows your custom commit as the booted deployment. If you are running the image in a container, use podman inspect to verify the environment variables and labels.

podman inspect my-custom-silverblue:v1 | grep -A 5 "Config"
# Inspect the container image metadata.
# grep filters the output to show the configuration section.
# Verify that environment variables and labels are present.

Check the logs if the system fails to boot or the container exits immediately.

journalctl -xeu rpm-ostree
# View logs for the rpm-ostree service.
# -x adds explanatory text, -e jumps to the end, -u filters by unit.
# This is the standard way to debug atomic update issues.

Reboot before you debug. Half the time the symptom is gone after a clean boot cycle.

Common pitfalls and what the error looks like

The podman pull command will refuse to proceed and print Error: cannot pull image: unable to pull image: no such image. This usually means the registry URL is wrong or the image tag does not exist. Check the registry URL and the tag spelling. If you are using a private registry, ensure you have authenticated with podman login.

If you see Error: parsing containers.conf: invalid character in the logs, your configuration file has a syntax error. containers.conf uses TOML syntax. Check for missing quotes, incorrect indentation, or invalid characters. Run podman info to validate the configuration.

Error: parsing containers.conf: invalid character
# This error indicates a syntax error in the TOML configuration file.
# Check for missing quotes, incorrect indentation, or invalid characters.
# Run podman info to validate the configuration and see the full error.

SELinux denials can block container operations if the policy is not configured correctly. SELinux denials show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux.

journalctl -t setroubleshoot
# Check for SELinux denial messages.
# The output includes a one-line summary and a link to the solution.
# Do not disable SELinux. Fix the policy or use the provided command.

Modifying files in the root filesystem of an Atomic Desktop will fail with Read-only file system. This is intentional. The OS is immutable. Use rpm-ostree kargs to change kernel arguments. Use rpm-ostree override to replace a package. Use distrobox or containers to run tools that require a mutable environment.

Trust the package manager. Manual file edits drift, snapshots stay.

When to use this vs alternatives

Use Universal Blue when you need a minimal base to compose your own image with specific packages and configurations.

Use Custom Silverblue images when you want to distribute a pre-configured environment to a team or automate deployments.

Use the official Fedora Silverblue release when you want a ready-to-use desktop with GNOME and standard packages.

Use Distrobox when you need to run tools in a container without modifying the host image.

Use rpm-ostree compose when you are building images in a CI/CD pipeline and need to generate OSTree commits from a Containerfile.

Stay on the upstream Workstation if you only deviate from the defaults occasionally.

Choose the image that matches your workflow. Don't build a custom image just to install one package.

Where to go next