Enabling autologin and setting a custom hostname

Make sure that you have completed the steps described in the initial setup page before starting this tutorial.

Provisioning Fedora CoreOS

Fedora CoreOS does not have a separate install disk. Instead, every instance starts from a generic disk image which is customized on first boot via Ignition.

Ignition config files are written in JSON but are typically not user-friendly. Configurations are thus written in a simpler format, the Butane config, that is then converted into an Ignition config. The tool responsible for converting Butane configs into Ignition configs is called Butane.

First Ignition config via Butane

Let’s create a small Butane config that will perform the following actions:

  • Add a systemd dropin to override the default serial-getty@ttyS0.service.

  • The override will make the service automatically log the core user in to the serial console of the booted machine.

  • Set the system hostname by dropping a file at /etc/hostname,

  • Add a bash profile that tells systemd to not use a pager for output.

We can create a config file named autologin.bu now as:

variant: fcos
version: 1.5.0
    - name: serial-getty@ttyS0.service
      - name: autologin-core.conf
        contents: |
          # Override Execstart in main unit
          # Add new Execstart with `-` prefix to ignore failure`
          ExecStart=-/usr/sbin/agetty --autologin core --noclear %I $TERM
    - path: /etc/hostname
      mode: 0644
        inline: |
    - path: /etc/profile.d/systemd-pager.sh
      mode: 0644
        inline: |
          # Tell systemd to not use a pager when printing information
          export SYSTEMD_PAGER=cat

This configuration can then be converted into an Ignition config with Butane:

butane --pretty --strict autologin.bu --output autologin.ign

The resulting Ignition configuration produced by Butane as autologin.ign has the following content:

  "ignition": {
    "version": "3.4.0"
  "storage": {
    "files": [
        "path": "/etc/hostname",
        "contents": {
          "compression": "",
          "source": "data:,tutorial%0A"
        "mode": 420
        "path": "/etc/profile.d/systemd-pager.sh",
        "contents": {
          "compression": "",
          "source": "data:,%23%20Tell%20systemd%20to%20not%20use%20a%20pager%20when%20printing%20information%0Aexport%20SYSTEMD_PAGER%3Dcat%0A"
        "mode": 420
  "systemd": {
    "units": [
        "dropins": [
            "contents": "[Service]\n# Override Execstart in main unit\nExecStart=\n# Add new Execstart with `-` prefix to ignore failure`\nExecStart=-/usr/sbin/agetty --autologin core --noclear %I $TERM\n",
            "name": "autologin-core.conf"
        "name": "serial-getty@ttyS0.service"

Butane outputs valid Ignition configs. However, if you are tweaking the config after Butane, or manually creating Ignition configs, you will have to verify that the config format is valid with ignition-validate:

ignition-validate autologin.ign && echo 'Success!'

Booting Fedora CoreOS

Now that we have an Ignition config, we can boot a virtual machine with it. This tutorial uses the QEMU image with libvirt, but you should be able to use the same Ignition config on all the platforms supported by Fedora CoreOS.

We use virt-install to create a new Fedora CoreOS virtual machine with a specific config:

# Setup the correct SELinux label to allow access to the config
chcon --verbose --type svirt_home_t autologin.ign

# Start a Fedora CoreOS virtual machine
virt-install --name=fcos --vcpus=2 --ram=2048 --os-variant=fedora-coreos-stable \
    --import --network=bridge=virbr0 --graphics=none \
    --qemu-commandline="-fw_cfg name=opt/com.coreos/config,file=${PWD}/autologin.ign" \

The virt-install command will start an instance named fcos from the fedora-coreos.qcow2 image using the autologin.ign Ignition config. It will auto-attach the serial console of the machine so you will be able to see the image bootup messages.

We use the backing_store option to virt-install --disk to quickly create a new disk image and avoid writing to the original image we have downloaded. This new disk image can be easily thrown away.

Depending on your version of virt-install, you may not be able to use --os-variant=fedora-coreos-stable and will get an error. In this case, you should pick an older Fedora variant (--os-variant=fedora31 for example). You can find the variants that are supported by you current version of virt-install with osinfo-query os | grep '^\s*fedora'.

Once the machine is booted up you should see a few prompts and then you should be automatically logged in and presented with a bash shell:

Fedora CoreOS 38.20230709.3.0
Kernel 6.3.11-200.fc38.x86_64 on an x86_64 (ttyS0)

SSH host key: SHA256:Eq0GiuflXh/3E+9h689DV4K2C0VQZ5UsXXfbJ7nB4rw (ECDSA)
SSH host key: SHA256:53uunBzHa2kfCO20q8h4cFeM19QRSscwUWUPoL4BP+4 (ED25519)
SSH host key: SHA256:HXrypq4OjKQ267RPhpptulMMYwsnrVWW3PYuvkIyt3k (RSA)
Ignition: ran on 2023/08/03 15:59:14 UTC (this boot)
Ignition: user-provided config was applied
No SSH authorized keys provided by Ignition or Afterburn
tutorial login: core (automatic login)

Fedora CoreOS 38.20230709.3.0
[core@tutorial ~]$

Let’s verify that our configuration has been correctly applied. As we were automatically logged in to the terminal, we can safely assume that the systemd dropin has been created:

[core@tutorial ~]$ systemctl cat serial-getty@ttyS0.service
# /usr/lib/systemd/system/serial-getty@.service

# /etc/systemd/system/serial-getty@ttyS0.service.d/autologin-core.conf
# Override Execstart in main unit
# Add new Execstart with `-` prefix to ignore failure`
ExecStart=-/usr/sbin/agetty --autologin core --noclear %I $TERM

We can also check that the hostname has correctly been set:

[core@tutorial ~]$ cat /etc/hostname
[core@tutorial ~]$ hostnamectl
     Static hostname: tutorial
           Icon name: computer-vm
             Chassis: vm πŸ–΄
          Machine ID: fc4c5d5a14a741babe20559a25dcb846
             Boot ID: 22ed3b3c049d42968fb6ca9e35c8055d
      Virtualization: kvm
    Operating System: Fedora CoreOS 38.20230709.3.0
         CPE OS Name: cpe:/o:fedoraproject:fedora:38
      OS Support End: Tue 2024-05-14
OS Support Remaining: 9month 1w 3d
              Kernel: Linux 6.3.11-200.fc38.x86_64
        Architecture: x86-64
     Hardware Vendor: QEMU
      Hardware Model: Standard PC _Q35 + ICH9, 2009_
    Firmware Version: 1.16.2-1.fc38
       Firmware Date: Tue 2014-04-01

Exploring Fedora CoreOS internals

Once we have access to the console of the machine we can browse around a bit to see some of the different pieces of the operating system. For example, even though this is an OSTree based system it was still composed via RPMs and we can inspect the system to see what it was composed of:

[core@tutorial ~]$ rpm -q ignition kernel moby-engine podman systemd rpm-ostree zincati

We can also inspect the current revision of Fedora CoreOS:

[core@tutorial ~]$ rpm-ostree status
State: idle
AutomaticUpdatesDriver: Zincati
  DriverState: active; periodically polling for updates (last checked Thu 2023-08-03 15:59:23 UTC)
● fedora:fedora/x86_64/coreos/stable
                  Version: 38.20230709.3.0 (2023-07-24T12:25:01Z)
                   Commit: 552de26fe0fe6a5e491f7a4163db125e3d44b144ae53a8f5f488e3f8481c46f9
             GPGSignature: Valid signature by 6A51BBABBA3D5467B6171221809A8D7CEB10B464

And check on zincati.service, which communicates with our update server and tells rpm-ostree when to do an update and to what version to update to:

[core@tutorial ~]$ systemctl status --full zincati.service
● zincati.service - Zincati Update Agent
     Loaded: loaded (/usr/lib/systemd/system/zincati.service; enabled; preset: enabled)
    Drop-In: /usr/lib/systemd/system/service.d
     Active: active (running) since Thu 2023-08-03 16:06:39 UTC; 18s ago
       Docs: https://github.com/coreos/zincati
   Main PID: 1843 (zincati)
     Status: "periodically polling for updates (last checked Thu 2023-08-03 16:06:39 UTC)"
      Tasks: 6 (limit: 2238)
     Memory: 2.8M
        CPU: 257ms
     CGroup: /system.slice/zincati.service
             └─1843 /usr/libexec/zincati agent -v

Aug 03 16:06:39 tutorial systemd[1]: Starting zincati.service - Zincati Update Agent...
Aug 03 16:06:39 tutorial zincati[1843]: [INFO  zincati::cli::agent] starting update agent (zincati 0.0.25)
Aug 03 16:06:39 tutorial zincati[1843]: [INFO  zincati::cincinnati] Cincinnati service: https://updates.coreos.fedoraproject.org
Aug 03 16:06:39 tutorial zincati[1843]: [INFO  zincati::cli::agent] agent running on node '8fb5386cba574235a21ad3b2d59885d9', in update group 'default'
Aug 03 16:06:39 tutorial zincati[1843]: [INFO  zincati::update_agent::actor] registering as the update driver for rpm-ostree
Aug 03 16:06:39 tutorial zincati[1843]: [INFO  zincati::update_agent::actor] initialization complete, auto-updates logic enabled
Aug 03 16:06:39 tutorial zincati[1843]: [INFO  zincati::strategy] update strategy: immediate
Aug 03 16:06:39 tutorial systemd[1]: Started zincati.service - Zincati Update Agent.
Aug 03 16:06:39 tutorial zincati[1843]: [INFO  zincati::update_agent::actor] reached steady state, periodically polling for updates
Aug 03 16:06:41 tutorial zincati[1843]: [INFO  zincati::cincinnati] current release detected as not a dead-end

One other interesting thing to do is view the logs from Ignition in case there is anything interesting there we may want to investigate:

journalctl -t ignition

And finally, of course we can use the podman (or docker) command to inspect the current state of containers on the system:

podman version
podman info
podman commands can be run as root or as non-root user. docker commands need to be run as root via sudo unless the user has been added to the docker group.
Running containers via docker and podman at the same time can cause issues and result in unexpected behaviour. Refer to the FAQ Entry for more details.
The Docker daemon is not started by default but running any docker command will start it as it is socket activated via systemd.

Taking down the Virtual Machine

Let’s now get rid of that virtual machine so we can start again from scratch. First escape out of the serial console by pressing CTRL + ] and then type:

virsh destroy fcos
virsh undefine --remove-all-storage fcos

You may now proceed with the second tutorial.