Butane to Ignition - How to Generate and Convert Your Configuration Files
In the video below, we show you the simpler way to create Ignition files
Prerequisites
- •Linux experience
- •Container fundamentals
What You'll Learn
- Deploy and manage Fedora CoreOS
- Work with container-optimized Linux systems
Fedora CoreOS
Butane to Ignition - How to Generate and Convert Your Configuration Files
Jan 29, 2026
· 9 mins read
_
#### In the video below, we show you the simpler way to create Ignition files
Fedora CoreOS requires an Ignition file as part of the installation setup, but it’s in JSON and not so easy to read
I asked an AI to create an Ignition file for me but it didn’t work
When I pointed out the error to the AI, it corrected it, but to me that defeated the purpose
Now Butane is the name of the tool which we’re told is used to consume a Butane config file and produce an Ignition file
The Butane config file is written in YAML and much easier for us mere humans to understand
So if you want a basic installation of Fedora CoreOS, how do you use Butane to create the Ignition file you need?
Well, if that’s something you’re interested in finding out, then stick around and watch this video as that’s what we’ll be going over
Useful links:
https://github.com/coreos/butane/blob/main/docs/getting-started.md https://github.com/coreos/butane/blob/main/docs/specs.md
Expectations:
To me, the basic premise behind CoreOS is that once the OS is installed you don’t add or remove software packages
And the OS will automatically keep itself up to date
Instead you should create containers using Podman, which is installed by default, although Docker is another option if you set that up
So my goal is to install CoreOS on a machine and configure it just enough to let Ansible make various changes
In other words, a non-root user will manage containers, and I want Ansible to manage that account as well as to manage containers through that non-root account
In addition, Ansible needs to manage the settings of things like the NTP and DNS server as part of an automated way to standardise settings across platforms
In which case, I need to supply each server with an Ignition file that does just enough
But first we have to create a Butane file
Butane File:
Butane config files are YAML files based on Butane’s schema
So an example of a base config I’ll be using for my own hosting servers would be as follows
nano srv1.bu
variant: fcos
version: 1.6.0
passwd:
users:
- name: ansible
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFhu2XQ6mrauquujA37CFoGvihBTC9Y2MpltXDth5250 ansible@homelab.lanstorage:
links:
- path: /etc/localtime
target: ../usr/share/zoneinfo/Europe/London
files:
- path: /etc/hostname
mode: 0644
contents:
inline: srv1
- path: /etc/NetworkManager/system-connections/ens18.nmconnection
mode: 0600
contents:
inline: |
[connection]
id=ens18
type=ethernet
interface-name=ens18
[ipv4]
address1=192.168.1.7/24,192.168.1.254
dns=192.168.1.10;
method=manual
[ipv6]
method=disabled
- path: /etc/sysctl.d/60-disable-ipv6.conf
contents:
inline: |
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1
- path: /etc/sudoers.d/ansible
mode: 0440
contents:
inline: "ansible ALL=(ALL) NOPASSWD: ALL"
- path: /etc/chrony.conf
overwrite: true
contents:
inline: |
server 192.168.1.10 iburst
driftfile /var/lib/chrony/drift
makestep 1.0 3
rtcsync
- path: /etc/zincati/config.d/50-reboot-strategy.toml
mode: 0644
contents:
inline: |
[updates]
strategy = "periodic"
[updates.periodic]
time_zone = "localtime"
[[updates.periodic.window]]
days = [ "Sun" ]
start_time = "03:00"
length_minutes = 60
- path: /etc/vconsole.conf
mode: 0644
contents:
inline: KEYMAP=uk
Now save and exit
What I want from this is to create a user account for Ansible which will login using SSH key authentication
Typically that means it has no password, and you can only login if you have the SSH private key
Now if you’ve no need for Ansible, put your user account into the wheel group instead, for instance
users:
- name: myuser
groups:
- wheel
ssh_authorized_keys:
- ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFhu2XQ6mrauquujA37CFoGvihBTC9Y2MpltXDth5250 myuser@homelab.lanThe timezone is something that Ansible will keep in synch over time, but I still prefer to set that during the initial installation
Each computer needs its own hostname, so we’ll set that in this Butane file
Static IP addressing is preferred, because if the DHCP service goes down, after time so would any server relying on it. So we’ll apply a static IP address along with other IP settings
Bear in mind, the interface name is referenced in three lines of code here so you need to be careful when changing this to suit your own computer
- path: /etc/NetworkManager/system-connections/ens18.nmconnection
id=ens18
interface-name=ens18During my IT career nobody was really interested in IPv6, and so although I regularly did exams on it, this was never put to use
I still don’t have a need for it myself, so by general consensus I disable IPv6
The argument being, the less code, the less chance for bugs and security vulnerabilities; “If you don’t need it, don’t enable it”
But if you want to use IPv6, I’ve disabled it in the interface section, so you’ll need to change this
- path: /etc/NetworkManager/system-connections/ens18.nmconnection [ipv6]
method=disabled
As an extra mesasure, I’ve disabled IPv6 elsewhere in the configuration, so you’ll want to remove this as well as I want this disabled system wide
- path: /etc/sysctl.d/60-disable-ipv6.conf
contents:
inline: |
net.ipv6.conf.all.disable_ipv6 = 1
net.ipv6.conf.default.disable_ipv6 = 1Now typically Ansible needs elevated rights and to be able to execute privileged commands without requiring a password, otherwise any automation would fail as it would need intervention
But when it comes to CoreOS, typically an admin user is made a member of wheel, hence why I suggested that before
So why do I not just put this user in the wheel group?
Well it’s because sudo is used on other platforms I have and I want to keep the automation consistent across all of them so I can maintain them from the same playbook
So instead, I’ve granted the Ansible user sudo rights by adding a file in /etc/sudoers.d/
But if you’ve no need for that then delete that section
For security reasons, I block access to Internet NTP servers, so I’ve re-configured Chrony to use an internal NTP server
Granted Ansible might be used to change this at some point in the future, but the computer needs to begin with something to synch its clock to
Now CoreOS will keep itself up to date, but I want to limit when the computer will reboot, otherwise it will update and immediately reboot which can be disruptive
In which case, we’ll set a date and time for when reboots are allowed
So in this example, it means that even if there’s an update staged on Monday, the computer won’t reboot until somewhere between 03:00 and 04:00 on Sunday
But bear in mind, this is very strict
If you reboot the computer prior to that date and time, it will not use the opportunity to switch to the new version
And if misses the window, due to say a power outage on Sunday, you’ll then have to wait until the next Sunday before it will switch over automatically
Fortunately though you can manually override this if an upgrade becomes a priority
Like other minimal operating systems I’ve noticed it complains about the Locale missing during the installation, although it will revert to C.UTF-8
So I looked into changing this but it requires adding a language pack
In addition, Ansible relies on Python 3 on the host to then run a script on the target host
Now I layered the extra software required, as part of an initial build and everything was working fine
But it lead to a whole heap of upgrade problems due to library version mismatches and the way in which CoreOS is designed
The expectation is if you want to run applications, run them as containers
Now I can change the keyboard without issue and that’s extremely important
But since the expectation is to run applications as containers, trying to shoehorn a different Locale into the host OS isn’t worth the changes it would bring
Python3 is a different story as I need to use Ansible. But there are ways and means to deal with that, without making OS changes
As an aside, by default CoreOS has a user called core which you can use, but I create my own super user account because core is public knowledge and it’s usually harder to hack a computer if you don’t even know the user account
Transpiling:
Like I said before, Butane is a tool to consume a Butane config file and produce an Ignition file
But I must admit the word transpiling just doesn’t sit well with me
It sounds more like someone travelling country to country putting telegraph poles in the ground
Yet that’s the definition of using the Butane tool to create an Ignition file
You can set this up as a container but what if you don’t have anything to run containers?
Well you can also download this as a binary and use that instead and that makes more sense when you’re starting from scratch
In which case, we’ll do that
curl -L https://github.com/coreos/butane/releases/latest/download/butane-x86_64-unknown-linux-gnu -o butane
chmod +x butane
sudo mv butane /usr/local/bin/In other words, we download the file, make it executable then move it to /usr/local/bin where it’s more accessible
First we’ll test that it can be found and used
butane --versionThen we’ll use it to create our Ignition file
butane --strict --output srv1.ign srv1.buWe use –strict because we want this to fail if there are any issues with this Butane file; The last thing we need is a an Ignition file with syntax errors for instance
And we use –output to create the Ignition file, as otherwise the result will just be output to the screen
To give you some perspective, this is what the Ignition file looks like
cat srv1.ign
{"ignition":{"version":"3.5.0"},"passwd":{"users":[{"name":"ansible","sshAuthorizedKeys":["ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIFhu2XQ6mrauquujA37CFoGvihBTC9Y2MpltXDth5250 ansible@homelab.lan"]}]},"storage":{"files":[{"path":"/etc/hostname","contents":{"compression":"","source":"data:,srv1"},"mode":420},{"path":"/etc/NetworkManager/system-connections/ens18.nmconnection","contents":{"compression":"gzip","source":"data:;base64,H4sIAAAAAAAC/0zMwarCMBCF4f08y73VKbVWJE9Suhg7RxpoJiUZBd/ehYouz+HjH+dshtljtomiBljlgfyxIcAXFINTNEe5yox/k4Q3oTFu924iUS2olQOf2ob7oeHmuGu7v+9sDx2p1R/A+zMl+JI1JLGbrK9aP31ejVUuK5SeAQAA//9rlDtpnwAAAA=="},"mode":384},{"path":"/etc/sysctl.d/60-disable-ipv6.conf","contents":{"compression":"","source":"data:,net.ipv6.conf.all.disable_ipv6%20%3D%201%0Anet.ipv6.conf.default.disable_ipv6%20%3D%201%0A"}},{"path":"/etc/sudoers.d/ansible","contents":{"compression":"","source":"data:,ansible%20ALL%3D(ALL)%20NOPASSWD%3A%20ALL"},"mode":288},{"overwrite":true,"path":"/etc/chrony.conf","contents":{"compression":"","source":"data:,server%20192.168.1.10%20iburst%0Adriftfile%20%2Fvar%2Flib%2Fchrony%2Fdrift%0Amakestep%201.0%203%0Artcsync%0A"}},{"path":"/etc/zincati/config.d/50-reboot-strategy.toml","contents":{"compression":"gzip","source":"data:;base64,H4sIAAAAAAAC/2TNMQrDMAyF4V2nED5AMBQ6FHKKjsEYE4tU4MjBlgnp6YuGTF1/Pukt48hJqQfo2pLSduGM7qDGNfPqAG4w3S2A8k7xW4WMlrqmYsXsH55OllzPECCnq+OMC7r3EIe2l5pGu7Q3/vHy3kEh2fQTd5ahZP7p4RcAAP//CiAq/qMAAAA="},"mode":420},{"path":"/etc/vconsole.conf","contents":{"compression":"","source":"data:,KEYMAP%3Duk"},"mode":420}],"links":[{"path":"/etc/localtime","target":"../usr/share/zoneinfo/Europe/London"}]}}That maybe be fine for a computer, but definitely not for us
So if you want one final check run this command
butane --pretty --strict srv1.buThis time you’ll see the output in JSON but in a more easier to read format
But in any case, hopefully, you’ll now find it much easier to create Ignition files by using Butane
Sharing is caring!_
Please enable JavaScript to view the comments powered by Disqus.