Fedora

Install Podman Compose on Fedora 45 / 44 and Run Rootless Stacks

Podman Compose lets you run a compose.yaml stack on Fedora without Docker, without a daemon and without root. You write the same Compose file you would hand to Docker and run podman-compose up -d. By the end you’ll have a three-service stack (Caddy, a small web app and Redis with a health check) that answers on port 8080, takes requests from other machines and comes back on its own after a reboot.

Original content from computingforgeeks.com - post 167800

The install is the easy part. What trips you up is the Podman 6 removal of slirp4netns, the SELinux label on bind mounts, podman compose quietly picking Docker Compose, and stacks that stop when you log out, so each gets a step with the real error you will see. I went through every step again in October 2026 with podman-compose 1.6.0 on Fedora 45 (Podman 6.1) and Fedora 44 (Podman 5.8), and they still hold.

Check what Fedora 45 and Fedora 44 ship

Both releases package the same podman-compose release, so the Compose side of this guide is identical on each. The difference sits underneath: Fedora 45 moved to Podman 6, which removed slirp4netns completely, while Fedora 44 stays on Podman 5. Both already use pasta for rootless networking, netavark for container networks and SQLite for the container database, so a fresh install behaves the same until a Compose file asks for something Podman 6 no longer has.

ItemFedora 45Fedora 44
podman-compose package1.6.0-2.fc451.6.0-1.fc44
Podman6.1.35.8.9
Packages pulled by the install4 (podman-compose, python3-click, python3-dotenv, python3-dotenv+cli)4 (same set)
Rootless network backendpastapasta
network_mode: slirp4netnsError: support has been removedError until you install the slirp4netns package
Rootless published port from another machineRefused until you open it in firewalldSame

If you’re upgrading a Fedora 44 machine that already runs containers, read how to fix Podman after a Fedora upgrade first, because the database and network changes hit existing containers before any Compose file runs. The full upstream list of what Podman 6 dropped is in Podman 6 breaking changes.

Install Podman Compose on Fedora

podman-compose is in the main Fedora repository, and Podman itself comes along as a dependency if it isn’t there yet. If Podman is already there, as it was on the lab VMs, you’ll only see the Python pieces get installed. If you need the engine on its own first, the Podman install guide for Fedora covers registries, ports and Quadlet.

sudo dnf install -y podman-compose

Check both versions. podman-compose prints the Podman version it found next to its own, which is a quick way to confirm which major you’re on:

podman-compose version

On Fedora 45 you should see Podman 6 and podman-compose 1.6.0. Fedora 44 prints podman version 5.8.9 with the same podman-compose line.

podman version 6.1.3
podman-compose version 1.6.0

This is what the install looked like on the Fedora 45 VM, with the transaction trimmed to the package table. You only need four packages and about 2 MiB of disk:

Install podman-compose on Fedora 45 with dnf and check podman-compose version

You don’t need the podman-docker package for anything in this guide. It only adds a docker command that calls Podman, which helps old scripts but changes nothing about how Compose files run.

Choose what podman compose runs

You have two ways to start a stack, and they are not the same program. podman-compose (with the hyphen) is the Python tool you just installed. podman compose (with a space) is a thin wrapper built into Podman that runs an external provider. Before you install any provider, the wrapper fails and lists every path it searched:

Error: looking up compose provider failed
7 errors occurred:
	* exec: "/home/lorna/.docker/cli-plugins/docker-compose": stat /home/lorna/.docker/cli-plugins/docker-compose: no such file or directory
	* exec: "/usr/local/lib/docker/cli-plugins/docker-compose": stat /usr/local/lib/docker/cli-plugins/docker-compose: no such file or directory
	* exec: "/usr/local/libexec/docker/cli-plugins/docker-compose": stat /usr/local/libexec/docker/cli-plugins/docker-compose: no such file or directory
	* exec: "/usr/lib/docker/cli-plugins/docker-compose": stat /usr/lib/docker/cli-plugins/docker-compose: no such file or directory
	* exec: "/usr/libexec/docker/cli-plugins/docker-compose": stat /usr/libexec/docker/cli-plugins/docker-compose: no such file or directory
	* exec: "docker-compose": executable file not found in $PATH
	* exec: "podman-compose": executable file not found in $PATH

Look at the order in that list: every Docker Compose location comes before podman-compose. So the moment you install Fedora’s docker-compose package (it’s in the Fedora repo), podman compose switches to it without asking. In the lab, podman compose version went from printing podman-compose 1.6.0 to Docker Compose version 5.5.1, and Docker Compose then needs the Podman API socket, which isn’t running by default.

The part that really catches people out is that the two tools don’t see each other’s containers. With the socket running, podman compose ps through Docker Compose showed an empty table while three podman-compose containers were up in the same directory. If you start a stack with one tool, stop it with the same one. The easiest way to make that automatic is to pin the provider in your own containers.conf drop-in:

mkdir -p ~/.config/containers/containers.conf.d
vim ~/.config/containers/containers.conf.d/compose.conf

Add these three lines. The second setting also turns off the “Executing external compose provider” banner that otherwise prints before every command:

[engine]
compose_providers=["podman-compose"]
compose_warning_logs=false

Now podman compose and podman-compose run the same program, even with Docker Compose installed. For a one-off you can also set PODMAN_COMPOSE_PROVIDER=podman-compose in front of a single command. The rest of this guide uses podman-compose directly, so there’s no doubt which tool is running.

Write the compose.yaml file

The example stack has three services, and each one shows something you’ll use in your own projects. web is Caddy, which publishes port 8080 and reads a bind-mounted config file. app is a tiny whoami server that Caddy reaches by its service name. cache is Redis with a health check, and app waits for that check to pass before it starts. Create a project directory for it:

mkdir -p ~/compose-demo && cd ~/compose-demo
vim compose.yaml

Paste the stack definition. Every image uses its full registry path, which matters on Fedora for a reason covered in the errors section:

services:
  web:
    image: docker.io/library/caddy:2-alpine
    ports:
      - "8080:80"
    volumes:
      - ./Caddyfile:/etc/caddy/Caddyfile:Z
      - caddy_data:/data
    depends_on:
      app:
        condition: service_started
    restart: unless-stopped
  app:
    image: docker.io/traefik/whoami:latest
    depends_on:
      cache:
        condition: service_healthy
    restart: unless-stopped
  cache:
    image: docker.io/library/redis:8-alpine
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 5s
      timeout: 3s
      retries: 5
    restart: unless-stopped
volumes:
  caddy_data:

Next, create the Caddyfile in the same directory:

vim Caddyfile

It only needs to forward everything to the app service on port 80:

:80 {
    reverse_proxy app:80
}

Keep the :Z at the end of the Caddyfile mount. Fedora runs SELinux in enforcing mode, and :Z relabels the file so only this container can read it. Without it the stack still starts, but Caddy can’t open its own config and keeps restarting with nothing useful in the logs, which is the hardest failure in this guide to spot. Use a lowercase :z instead when two containers need to read the same host directory. There’s more on container labels in the SELinux survival guide for Fedora.

Start the stack and check it

Bring everything up in the background. The --wait flag, new in podman-compose 1.6, holds the prompt until each container is running and Redis reports healthy, so when your shell comes back the stack is ready rather than half started:

podman-compose up -d --wait

Once the images are cached, the whole stack is up in under 6 seconds. The output ends with the three container names and three lines of -1:

compose-demo_cache_1
compose-demo_app_1
compose-demo_web_1
-1
-1
-1

That -1 isn’t an error. --wait calls podman wait on each container, and the podman wait documentation says it prints -1 for any condition other than stopped or exited. Notice the names, too: podman-compose joins project, service and index with underscores (compose-demo_web_1), where Docker Compose uses hyphens.

Now list the services, call Caddy on the published port and check that the containers resolve each other by name:

podman-compose ps --format "{{.Names}}  {{.Status}}  {{.Ports}}"
curl -s http://localhost:8080/ | head -4
podman-compose exec web getent hosts app cache

You’re looking for (healthy) on the cache, 0.0.0.0:8080->80/tcp on web, and a whoami reply whose hostname is the app container’s ID. That reply proves Caddy found app through the project network’s DNS, and the getent lines show both service names resolving to 10.89.0.x addresses:

podman-compose ps, curl and service name DNS for a Caddy, whoami and Redis stack on Fedora 45

Behind the scenes podman-compose created ordinary Podman objects, so you can inspect them with plain podman commands as well:

  • You can see the project network as compose-demo_default in podman network ls.
  • You can see the named volume as compose-demo_caddy_data in podman volume ls.
  • You can read any service’s output with podman logs compose-demo_web_1, the same as podman-compose logs web.

That matters when you debug, because every Podman tool you already know works on a Compose stack without going through podman-compose.

Open the published port in firewalld

From the machine itself, port 8080 works straight away. From another computer it doesn’t, because a rootless port is never added to firewalld for you. If firewalld is running (Workstation and Server ship it, the Cloud image does not), the public zone on the lab VM allowed only ssh, mdns and dhcpv6-client, and Fedora Server’s FedoraServer zone allows ssh, dhcpv6-client and cockpit, so neither lets 8080 through. A request from the Fedora 44 VM was refused instantly:

curl: (7) Failed to connect to 10.0.1.50 port 8080 after 0 ms: Could not connect to server

Open the port permanently and reload the rules on the machine running the stack:

sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

Each command prints success, and the same request from the other machine now returns 200. Fedora Workstation uses the FedoraWorkstation zone, which already allows ports 1025 to 65535, so you can skip this step on a desktop. The firewalld guide for Fedora explains the zones if you’d rather allow the port only from one subnet.

Keep the stack running after logout and reboot

Most people hit this one on day two. restart: unless-stopped does not keep a rootless stack alive on its own. Your containers belong to your user’s systemd session, so when you close your last SSH session that session ends and takes the containers with it. Less than a minute after I logged out of the Fedora 45 VM, all three were down:

Linger=no
compose-demo_cache_1  Exited (0) 41 seconds ago
compose-demo_app_1  Exited (2) 41 seconds ago
compose-demo_web_1  Exited (0) 41 seconds ago

You need two settings to fix it, and you need both of them. Lingering keeps your user manager running when nobody is logged in, and podman-restart.service starts every container whose restart policy is always or unless-stopped when that manager starts at boot. Without the second one, the stack survives logout but not a reboot. After my first reboot test, with lingering on and podman-restart.service still disabled, all three containers sat at Exited.

sudo loginctl enable-linger $USER
systemctl --user enable podman-restart.service

Start the stack again with podman-compose up -d if it went down, then reboot to test it. When I logged back in right after boot, the containers had already been up for about 30 seconds and Caddy answered with 200, on Fedora 45 and Fedora 44 alike:

Podman Compose stack restarted by podman-restart.service after reboot on Fedora 45

This is the lightest way to keep a Compose stack up. If you want systemd to own each container, with dependencies, logs in the journal and automatic image updates, turn the services into Quadlet units instead. The Podman Quadlet guide for Fedora walks through that conversion.

Manage the stack day to day

Run these from the project directory, because podman-compose finds compose.yaml in the current folder. Each one was run against the demo stack in the lab:

podman-compose logs web
podman-compose restart web
podman-compose exec cache redis-cli ping
podman-compose pull
podman-compose up -d --force-recreate

Add -f to logs to follow the output live. Stopping the stack has two forms, podman-compose down and podman-compose down -v, so use these checks to tell them apart before you run either on real data:

  • You can run podman-compose down safely, because it removes the containers and the compose-demo_default network, and the caddy_data volume stays.
  • You should only run podman-compose down -v when you mean it, since on Fedora 44 it also deleted compose-demo_caddy_data, and podman volume ls came back empty.
  • You’ll get fresh images with pull followed by up -d --force-recreate, which replaces the containers but keeps the named volume.
  • You’ll see every logs line tagged with a colored [web] | prefix, even when the output goes to a pipe instead of your terminal.

If you also want a quick reference for the single-container commands behind all of this, the Podman cheat sheet lists them with real output.

Move a Docker Compose file to Podman

Most docker-compose.yml files run under podman-compose without edits, and the ones that don’t usually fail on one of four Fedora defaults. Go through this list once before your first up and you’ll skip most of the errors in the next section:

  • You’ve written every image as a full path such as docker.io/library/nginx:alpine, not just nginx:alpine.
  • You’ve added :Z (one container) or :z (shared) to every bind mount from your home directory.
  • You’ve replaced any network_mode: slirp4netns with network_mode: pasta, or removed the line so the project network is used.
  • You’ve published ports above 1023, or set net.ipv4.ip_unprivileged_port_start if the service really needs port 80 rootless.

Scripts that read container names need one more look, because compose-demo_web_1 isn’t compose-demo-web-1. If a project really needs Docker Engine itself, the Docker on Fedora guide covers both Docker CE and Fedora’s moby-engine package.

Fix common Podman Compose errors

Every message below was captured in the Fedora 45 or Fedora 44 lab while building this stack, with the change that fixed it.

Error: slirp4netns support has been removed, use –network=pasta instead

You’ll get this on Fedora 45 when a Compose file still has network_mode: slirp4netns, a line that was common in older rootless examples. Podman 6 refuses to create the container, so podman-compose’s next step fails too and the command exits with code 125. Change the line to network_mode: pasta, or delete it, and the same file starts and answers on its port:

Podman 6 slirp4netns support has been removed error in podman-compose on Fedora 45 and the pasta fix

On Fedora 44 the same file fails differently, with could not find slirp4netns, the network namespace can't be configured, because the slirp4netns package isn’t installed. Installing it would make the file work on Fedora 44 only, so switch to pasta on both releases.

Error: short-name resolution enforced but cannot prompt without a TTY

An image written without a registry, such as image: containous/whoami, could come from any of the three registries Fedora searches. Fedora also runs short names in enforcing mode, so it will not guess for you. When podman-compose runs without a terminal (a script, a CI job, a systemd unit or output piped into another command), Podman can’t ask which one you meant, so the pull stops with this error and the next line says the container wasn’t found. In an interactive shell the same up just sits there waiting for you to pick a registry. Popular names like nginx still work because Fedora ships an alias file for them, which is why the error looks random. Write the full path, docker.io/containous/whoami, and it goes away.

Container restarts forever and the logs show no error

This is the missing :Z. With the Caddyfile mounted without a label, the web container showed Up Less than a second, then Exited (1), and podman inspect counted 71 restarts in about a minute. Caddy’s log held only its two startup lines, and ausearch -m avc found nothing, so neither place pointed at SELinux. A direct read from inside the same image gives the real cause:

podman run --rm -v ./Caddyfile:/etc/caddy/Caddyfile docker.io/library/caddy:2-alpine cat /etc/caddy/Caddyfile

The file is visible but unreadable, and ls -Z shows why: it still carries user_home_t, while the working copy carries container_file_t after :Z relabeled it.

cat: can't open '/etc/caddy/Caddyfile': Permission denied

Add :Z to the mount and run podman-compose up -d again. Don’t reach for setenforce 0, because the label is the whole fix.

failed to connect to the docker API at unix:///run/user/1001/podman/podman.sock

You’ll see this when podman compose has picked Docker Compose as its provider and the Podman API socket isn’t running. Docker Compose talks to an API, it does not call the podman command. You can start the socket with systemctl --user enable --now podman.socket, but if you meant to use podman-compose, pin it with the compose.conf drop-in from earlier instead, because the two tools won’t manage each other’s containers.

Error: looking up compose provider failed

Neither podman-compose nor Docker Compose is installed, so podman compose has nothing to run. Install podman-compose with sudo dnf install -y podman-compose and the wrapper finds it in /usr/bin.

Containers exit a few seconds after you log out

If podman ps -a shows every container as Exited with the same age, check loginctl show-user $USER -p Linger. A value of no means your user manager stopped when your session ended. Turn on lingering and enable podman-restart.service as shown in the persistence step, and your stack will keep running whether you’re logged in or not.

Keep reading

How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) Fedora How to Upgrade Fedora 44 to Fedora 45 (GUI, CLI and Rollback) Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Fedora Enable RPM Fusion and Install Multimedia Codecs on Fedora 45 / 44 / 43 Harden Fedora 45 / 44 for Desktop and Server: Complete Guide Fedora Harden Fedora 45 / 44 for Desktop and Server: Complete Guide Switch Fedora From GRUB to systemd-boot With bootctl Fedora Switch Fedora From GRUB to systemd-boot With bootctl DNF5 Cheatsheet for Fedora 45 / 44: Tested Commands and Fixes Fedora DNF5 Cheatsheet for Fedora 45 / 44: Tested Commands and Fixes How to Use COPR Repositories on Fedora with dnf copr Fedora How to Use COPR Repositories on Fedora with dnf copr

Leave a Comment

Press ESC to close