Here’s how to schedule your Pi-hole container to run automatically on boot using a Quadlet file—no manual enabling required. These steps assume you’re on Fedora CoreOS as of February 2025, with a recent Podman version.
If you do not have CoreOS installed yet, start with the Beginner’s Guide to Fedora CoreOS (Butane, Ignition, first boot, firewall ports). The 2026 rewrite with Bitwarden SSH keys and Quadlets baked into Ignition is Beginner’s Guide to Fedora CoreOS (with Bitwarden SSH Keys). This post is the narrow piece: Pi-hole as a Quadlet on a box that already boots.
AdGuard fans: the sibling file is Running AdGuard Home as a Podman Quadlet. Same CoreOS idea, different image and ports.
Why Quadlets instead of podman run in a crontab?
Fedora CoreOS is an appliance OS. You should not SSH in after every reboot to start DNS. A Quadlet is a small .container unit in /etc/containers/systemd/ that Podman’s generator turns into a systemd service at boot. That gives you:
systemctl start/stop/status pihole.servicelike any other service.- Automatic restart policies.
- Volumes and ports declared in one file you can copy to the next machine.
- No Docker Compose on a host that is trying to stay minimal.
podman generate systemd is the older cousin. Quadlets are what CoreOS documentation expects now.
Prerequisites
- Fedora CoreOS, SSH as
core(or root via sudo). - Podman available (it is on FCOS).
- Ports 53, 80, 443, and optionally 67 free on the host. systemd-resolved’s stub listener on 127.0.0.53:53 will not collide with published 0.0.0.0:53, but a host process bound to
:53will. If you already followed the beginner CoreOS + Pi-holepodman runguide, stop and remove that container before the Quadlet tries to bind the same ports. - A static IP on the CoreOS box. DHCP that changes overnight makes every LAN client’s DNS setting wrong. I use
192.168.1.8in examples; use yours. - Timezone you actually live in. Logs and Pi-hole’s dashboard are miserable in UTC if you are not.
Check for port fights before you write the unit:
sudo ss -tulpn | grep -E ':53|:80|:443|:67'
sudo podman ps -a
If an old pihole container exists:
sudo podman stop pihole
sudo podman rm pihole
Keep the named volumes if they already have blocklists you care about (pihole_pihole, pihole_dnsmasq). The Quadlet below reuses those volume names.
Step 1: Create the Quadlet file
Fedora CoreOS uses /etc/containers/systemd/ for Quadlet files, which define containers as systemd services. Let’s create pihole.container:
sudo nano /etc/containers/systemd/pihole.container
Paste this (modify the password and network info with yours):
[Unit]
Description=Pi-hole Container
After=network-online.target
Wants=network-online.target
[Container]
ContainerName=pihole
Image=docker.io/pihole/pihole
AddCapability=NET_ADMIN
DNS=127.0.0.1
DNS=1.1.1.1
Environment=TZ=America/Denver
Environment=SERVERIP=192.168.1.8
Environment=FTLCONF_webserver_api_password=pwd_please_change_this
Environment=FTLCONF_dns_listeningMode=all
Environment=DNS1=1.1.1.1
Environment=DNS2=1.0.0.1
Environment=DNSSEC=true
Environment=CONDITIONAL_FORWARDING=true
Environment=CONDITIONAL_FORWARDING_IP=192.168.1.1
Environment=CONDITIONAL_FORWARDING_DOMAIN=lan
Volume=pihole_pihole:/etc/pihole:Z
Volume=pihole_dnsmasq:/etc/dnsmasq.d:Z
PublishPort=80:80/tcp
PublishPort=443:443/tcp
PublishPort=67:67/udp
PublishPort=53:53/tcp
PublishPort=53:53/udp
[Service]
Restart=always
TimeoutStartSec=900
[Install]
WantedBy=multi-user.target
Save and exit (Ctrl+O, Enter, Ctrl+X in nano).
Why these keys exist
After=/Wants=network-online.target: DNS for the whole house should not bind before the NIC has an address. On Wi-Fi appliances this matters more than on virtio.AddCapability=NET_ADMIN: DHCP (port 67) and some network trickery inside Pi-hole want it. If you will never use Pi-hole DHCP, you can drop it later; leave it for a first install.DNS=127.0.0.1plus an upstream: the container needs a resolver while it starts, before FTL owns port 53. A container with no DNS listed can hang on image pulls and list updates.FTLCONF_*: Pi-hole v6-style config. Older tutorials usedWEBPASSWORD. If the dashboard ignores your password, you mixed v5 and v6 variable names — check the image tag you actually pulled.Volume=name:/path:Z: named volumes plus SELinux relabel (:Z) so CoreOS does not mysteriously deny writes.TimeoutStartSec=900: first pull ofdocker.io/pihole/piholeon a slow link will exceed systemd’s default start timeout. Nine minutes is cheaper than a failed unit and a house without DNS.Restart=always: FTL crash should not require you to wake up.
Replace pwd_please_change_this, SERVERIP, TZ, and the conditional forwarding IP with your LAN router. Do not leave the sample password on a network other people can reach.
You can pre-create volumes (optional; Podman creates them on first start):
sudo podman volume create pihole_pihole
sudo podman volume create pihole_dnsmasq
Step 2: Reload systemd
Tell systemd to process the Quadlet file with Podman’s quadlet generator:
sudo systemctl daemon-reload
This generates a transient pihole.service in /run/systemd/generator/.
Expected output: no news is good news. If nano left a bad INI (missing section, PublishPort typo), daemon-reload still succeeds and start fails. Check:
systemctl cat pihole.service
ls /run/systemd/generator/pihole.service
No generated unit means the file is in the wrong directory or has the wrong suffix. It must be *.container under /etc/containers/systemd/ (or ~/.config/containers/systemd/ for rootless, which I do not recommend for port 53).
Step 3: Start the container
Kick it off manually the first time:
sudo systemctl start pihole.service
Watch the first pull:
sudo journalctl -u pihole.service -f
You should see Podman pulling layers, then FTL starting. Ctrl+C leaves the service running. Check it’s running:
sudo podman ps -a
sudo systemctl status pihole.service
You should see your pihole container up and humming, and the unit active (running).
Open the admin UI from a browser on the LAN:
http://192.168.1.8/admin
(or the hostname). Log in with the password you set. If the page never loads, jump to troubleshooting before you point the whole house at this box.
Step 4: Test autostart on reboot
The WantedBy=multi-user.target line ensures Pi-hole starts on boot. Test it:
sudo reboot
After reboot, SSH back in and verify:
sudo podman ps -a
sudo systemctl status pihole.service
If pihole is running, you’re set—no systemctl enable needed, as Quadlets handle autostart dynamically.
Point one client at 192.168.1.8 for DNS and query:
dig @192.168.1.8 google.com
dig @192.168.1.8 doubleclick.net
The first should return A records. The second often returns Pi-hole’s blocked address (0.0.0.0 or a NULL, depending on mode). Only after that works should you set DHCP on the router to hand out this DNS to everyone.
Notes
- Why no enable? Generated services in
/run/are transient and can’t be enabled traditionally. The[Install]section ties it to the boot process instead. - Tweaks: Older Quadlet guides mention
HostnameorRestartPolicy, but these are unsupported now—stick to the basics above. - Hostname: Defaults to
piholefromContainerName. If you needpi-hole, you’d need a custom workaround or a classic systemd service. - Image tag: pinning
docker.io/pihole/pihole:2025.x.xis saner for an appliance than:latestonce the box is in production. I leave:latestout of the sample so first-time readers copy fewer moving parts; add a tag when you care about reproducibility. - Firewall: CoreOS may not have firewalld until you layer it (see the beginner guide). If you did layer firewalld, open 53/tcp+udp, 80, 443, and 67 if you use Pi-hole DHCP. If you used nftables like the AdGuard post, copy that idea and restrict sources to your LAN subnet.
- Nightly reboot of the appliance: if you also want the host to reboot on a schedule, that is a host timer, not a Quadlet: How to Schedule Nightly Reboots on Fedora CoreOS. DNS will be down during the reboot window — pick 03:00, not 19:00.
Troubleshooting
start fails with port already allocated
Something else owns 53 or 80. sudo ss -tulpn again. Typical culprits: leftover podman run Pi-hole, AdGuard, or systemd-resolved stub on a surprising address. Do not run Pi-hole and AdGuard on the same IP and ports.
Container is exited immediately
sudo podman logs pihole and journalctl -u pihole.service. Password env vars for the wrong Pi-hole generation, or a volume from v5 that needs a migration, show up here. Read the log once before wiping volumes.
Clients have no DNS after a CoreOS update
Atomic updates + a Quadlet usually come back together. If they do not, systemctl status pihole.service after reboot. A Podman / netavark bump can change CNI behavior; daemon-reload and start after reading the unit status is the first move, not a reinstall of CoreOS.
SELinux denials on volumes
You omitted :Z or you bind-mounted a host path without :Z. Named volumes with :Z are the path of least pain on FCOS.
I want this in Ignition instead of nano after boot
Put the same file contents in your Butane storage.files for /etc/containers/systemd/pihole.container, like the AdGuard example in the 2026 CoreOS guide. Then the first boot already has DNS. Nano is how I still demo it so you can see the unit before you bake it in.
Wrap-up
Pi-hole on Fedora CoreOS does not need Docker Compose or a login hook. One Quadlet, a daemon-reload, a start, a reboot test, then point DHCP at the box. Keep the password unique, keep the IP static, and keep 53 from being shared with another resolver.
That’s it! Your Pi-hole is now a proper Fedora CoreOS citizen, managed via Podman Quadlets. Enjoy ad-free browsing.
Best regards,
The Linux IT Guy