aoitcloud

CoreOS

Provisioning CoreOS with an Ignition config

On CoreOS, there's no "create a user after boot." Configuration happens once, via an Ignition file. Here's how to write one with Butane and supply it at provisioning.

11 February 2025 4 min read

Every other OS on our list gives you a way to fix a botched setup after the fact – SSH in, run useradd, edit a config file, move on. CoreOS doesn’t work that way. Configuration happens once, at first boot, via a file called an Ignition config. Get it wrong and the fix isn’t “log in and adjust it” – it’s “correct the config and reprovision.” This post covers how to actually write one.

What Ignition actually does

Ignition is CoreOS’s provisioning tool. It runs very early during first boot, reads a configuration file, applies it, and then it’s done – it does not keep running in the background reconciling state the way a configuration management agent would. A single Ignition config can set up:

  • User accounts and their SSH authorised keys
  • Password hashes and sudo/group membership
  • Disk partitioning, filesystem creation, and mount points
  • Arbitrary files dropped onto the filesystem, with specified permissions and ownership
  • systemd units – including enabling or masking them

The file Ignition itself consumes is JSON. It’s a stable, versioned, machine-generated format – not something you’re meant to hand-write.

Butane: the human-friendly layer on top

Because raw Ignition JSON is tedious and error-prone to write by hand, the normal workflow is to author your config in Butane – a YAML format designed to be readable – and run it through the butane tool to translate it into Ignition JSON. You should never need to write Ignition JSON directly for routine provisioning; treat it as a compiled artefact, not something you edit.

Install Butane, or use it as a container, then convert a config with a command like this:

butane --pretty --strict config.bu > config.ign

--strict makes Butane fail loudly on warnings instead of silently ignoring them, which is worth having on for anything you’re about to boot a real server with.

A minimal example: one user, one SSH key, passwordless sudo

Here’s a Butane config that creates a single administrative user, adds their SSH public key, puts them in the wheel group (CoreOS’s sudo-enabled group), and allows passwordless sudo for that group:

variant: fcos
version: 1.7.0
passwd:
  users:
    - name: andrew
      groups:
        - wheel
      ssh_authorized_keys:
        - ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAI... andrew@laptop
storage:
  files:
    - path: /etc/sudoers.d/wheel-nopasswd
      mode: 0440
      contents:
        inline: |
          %wheel ALL=(ALL) NOPASSWD: ALL

A few things worth noting about this example:

  • Replace the ssh_authorized_keys value with your own actual public key – never a private key, and never a placeholder you forget to swap out
  • CoreOS has no root password login by default and no password-based SSH login by default – key-based auth via Ignition is the intended path in, not an optional hardening step
  • The variant and version fields pin this config to a specific Butane spec – check the current Fedora CoreOS documentation for the latest supported version before you use this in production

Convert it, and inspect the output before you trust it with a real server:

butane --pretty --strict config.bu > config.ign
cat config.ign

Supplying the config at provisioning

The compiled config.ign file is what you hand to the platform provisioning your VPS – typically as user-data, the same mechanism cloud-init based images use, though CoreOS’s own Ignition consumes it directly rather than through cloud-init. When you provision a CoreOS instance with us, paste or upload the contents of config.ign into the user-data field at creation time. Ignition reads it on first boot, applies it once, and your instance comes up already configured – no console access, no manual setup step required.

If you need to change something afterwards – add a user, rotate a key, add a systemd unit – the workflow is to update your Butane source, recompile it, and provision a fresh instance rather than hand-editing the running machine. Forcing Ignition to rerun on an existing instance via the ignition.firstboot kernel flag is technically possible but unsupported and known to misbehave (systemd unit enablement in particular doesn’t reapply correctly), so don’t rely on it. Treat your Butane file as the source of truth, kept in version control, not the live server.

Get in touch

Drop our team a message today