What Is Fedora CoreOS and What Is It Used For?

Fedora CoreOS is a minimal, automatically-updating Linux OS designed for running containerized workloads reliably at scale.

You need a host that never drifts

You provision a container host, deploy your services, and walk away. Three months later, a security bulletin drops. On a traditional Linux box, you run dnf upgrade, hope nothing breaks, and spend an hour debugging a dependency conflict. On Fedora CoreOS, the system already downloaded the patch, verified it, and is waiting for a maintenance window to reboot into a fresh, known-good image. If the new image fails to start your containers, the system rolls back automatically. You never touched a package manager. You never edited a config file in place. The OS stayed out of your way.

What Fedora CoreOS actually does

Fedora CoreOS treats the operating system as a single, immutable artifact. Instead of installing packages one by one, the entire OS is built into a read-only disk image. When an update arrives, the system does not patch files in place. It writes the new image to a separate boot partition and switches to it on the next reboot. This is atomic. Either the new image boots perfectly, or it does not. There is no half-upgraded state where half your services are running version 2.4 and the other half are stuck on 2.3.

Think of it like swapping out a car engine. You do not replace pistons while the car is driving. You pull the old engine, drop in the new one, and start the car. If the new engine fires up, you keep it. If it stalls, you swap the old one back in. Fedora CoreOS uses dual boot slots to make this safe. The rpm-ostree tool manages these image layers, and zincati handles the background update checks.

Provisioning works differently too. Traditional Fedora uses Kickstart, which runs a script during installation. CoreOS uses Ignition. Ignition is a JSON configuration file that runs exactly once at first boot. It creates users, drops SSH keys, writes systemd unit files, and formats disks. After that first boot, the root filesystem is locked. You cannot run dnf install or edit /etc/ manually. Any change you make to /etc/ gets overwritten on the next reboot because the system merges the Ignition config back into the read-only base. If you need to change a setting, you update the Ignition file, apply it, and reboot.

Remember the Linux convention for configuration files. Files in /usr/lib/ ship with the package and get overwritten on updates. Files in /etc/ are meant for user modifications. On CoreOS, /etc/ is still user-controlled, but it is generated from Ignition at boot. Manual edits are temporary.

Run rpm-ostree status before you assume the system is idle. Immutable systems hide their activity until you ask.

How to provision and run it

You will not install Fedora CoreOS from a desktop installer. You generate a machine-readable configuration, compile it, and pass it to the hypervisor or bare-metal bootloader. The community uses Butane to write human-friendly YAML, which compiles down to strict Ignition JSON. Butane catches syntax errors early and enforces the Ignition schema.

Here is how you compile a basic configuration that creates a user and enables SSH:

# Install the Butane compiler on your workstation
sudo dnf install butane

# Compile the YAML config into strict Ignition JSON
# --pretty makes the output readable for debugging
# --strict catches syntax errors before you deploy
butane --pretty --strict my-host.bu > my-host.ign

# Verify the generated JSON matches the expected schema
butane --strict my-host.bu | jq .

Pass the my-host.ign file to your virtual machine as a kernel command line argument or via the cloud-init user-data mechanism. The hypervisor injects it at boot. Ignition reads it, applies the changes, and hands control to systemd. The system then starts zincati, which quietly checks for OS updates in the background.

Check the Ignition logs immediately after first boot. A misconfigured JSON file will halt the system before systemd even starts.

Verify the deployment

Once the system boots, you need to confirm the OS image is active, the update agent is running, and your containers are healthy. The rpm-ostree command shows the current deployment slot and the pending deployment if an update is staged.

# Show the active OS version and deployment slots
rpm-ostree status

# Check if the automatic update agent is running
systemctl status zincati.service

# Review recent update checks and download progress
journalctl -xeu zincati.service

The zincati service respects maintenance windows. It will not reboot a production node at 3 PM on a Tuesday. It waits for the configured window, verifies the new image boots successfully, and then marks the slot as stable. If you need to force an immediate check, you can trigger it manually, but let the agent do its job in production. Most sysadmins type journalctl -xeu <unit> muscle-memory style because the x flag adds explanatory text and the e flag jumps to the end. Use it here.

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

Common pitfalls and what the error looks like

The most common mistake is trying to use dnf to install software. The command will refuse to proceed and print Error: transactional-update is not available on this system. The base OS is not meant to be modified with traditional package managers. You run your applications inside containers, or you layer specific RPMs using rpm-ostree install if you absolutely must. Layering works, but it breaks the atomic update model. The system will have to rebase your layers on every OS update, which slows down reboots and increases disk usage.

Another trap is editing configuration files directly on the host. You open /etc/ssh/sshd_config, change a setting, and restart the service. It works until the next reboot. The system merges the Ignition config back into /etc/, overwriting your manual change. You will see your service fail to start or revert to defaults. The fix is to update the Butane YAML, recompile the Ignition JSON, and apply it.

If a new OS image breaks your container runtime, the system will fail to boot past the early systemd targets. You will see a GRUB menu or a rescue shell. Run the rollback command from the rescue prompt or from a working terminal before the reboot window closes.

# Revert to the previous known-good deployment slot
sudo rpm-ostree rollback

# Reboot immediately into the rolled-back image
sudo reboot

The rollback swaps the boot flag to the previous partition. Your data in /var and /home survives the switch. Only the OS image changes. SELinux denials will still show up in journalctl -t setroubleshoot with a one-line summary. Read those before disabling SELinux or relaxing container policies.

Snapshot the system before the upgrade. Future-you will thank you.

Pick the right Fedora variant

Fedora offers several variants. Each one targets a different operational model. Pick the one that matches your workflow.

Use Fedora CoreOS when you run containerized workloads and want the host OS to update automatically without manual intervention. Use Fedora Silverblue when you need a desktop environment with the same immutable, atomic update model and rollback safety. Use Fedora Workstation when you develop software, install kernel modules, or need full control over /etc/ and dnf. Use Fedora Server when you run traditional systemd services, databases, or legacy applications that require direct package installation. Stay on the upstream Workstation if you only deviate from the defaults occasionally and prefer a familiar package manager.

Where to go next