Docker

How to Install Docker on Fedora 45 / 44 / 43

Type sudo dnf install docker on Fedora and you don’t get Docker’s build at all. The name resolves to moby-engine, Fedora’s own package of the same open source engine, and on our Fedora 45 box it had caught up with Docker’s own engine release. The reason to install Docker CE from Docker’s repo is the plugins: Compose and Buildx on the day Docker ships them, the new Model Runner, and the exact behaviour Docker’s docs describe.

Original content from computingforgeeks.com - post 4554

Below, you install Docker on Fedora from Docker’s dnf5 repository and keep Podman working next to it. Then you turn on SELinux labels, close the firewalld gap that published ports open, and run rootless where root is off the table. Every step was run again on Fedora 45 in November 2026 with Docker Engine 29.9.2 and Compose 5.7.0, with Buildx and the Model Runner plugin from Docker’s Fedora 45 tree.

Pick Docker CE, moby-engine or Podman

Fedora gives you three ways to run containers. Docker CE and Podman live side by side, while moby-engine and the podman-docker shim each clash with Docker CE, as the sections below show. Docker CE is the right pick when your team, CI scripts or Compose files target Docker itself, because you get Docker’s packages on Docker’s release schedule. The table shows each option as it stood on the Fedora 45 VM the day we tested.

OptionWhere it comes fromVersion on Fedora 45Pick it when
Docker CEdownload.docker.com29.9.2, Compose 5.7.0, Buildx 0.38.1you want upstream Docker and its plugins
moby-engineFedora repos29.9.2 (Compose 5.6.0 as docker-compose)policy forbids third-party repos
PodmanFedora repos, preinstalled on Fedora Cloud6.1.3rootless, daemonless, systemd-native containers

Fedora’s packaging is closer to upstream than Ubuntu’s archive, where docker.io trailed by eight minor releases in our multi-distro Docker install guide. So moby-engine is a reasonable choice on a locked-down box. For everyone else, Docker CE keeps the docs, the plugins and the package names lined up, and the rest of this page uses it.

Check what the host needs

Docker Engine itself is light, so the containers you plan to run decide the sizing. Since Docker 29, a fresh install keeps image layers in the containerd image store under /var/lib/containerd, not in /var/lib/docker: after this lab, containerd held 596 MB of images and Docker’s own directory held 131 MB. Size the root or /var filesystem for that. Before you start, check the host has the following.

  • Fedora 45, 44 or 43, x86_64 or aarch64
  • a user with sudo rights
  • outbound HTTPS to download.docker.com and Docker Hub
  • about 400 MB for the packages, plus room for images under /var/lib/containerd

Our VM had 2 vCPU, 4 GB of RAM and a 40 GB disk, which is a floor for following along, not a production recommendation. If you are still on Fedora 42, upgrade first, because Docker’s Fedora 42 repo stopped at 29.5.3 while 43, 44 and 45 receive the current release. Fedora supports jumping two releases, so a 42 host can go straight to 44 with the same steps as the Fedora 43 to 44 upgrade, then on to 45 with the Fedora 44 to 45 upgrade. And if images, layers and networking are still new to you, one of the Docker books we recommend will save you a few evenings of reading error messages.

Remove old Docker packages

Docker’s repo conflicts with older Docker builds that may linger on a long-lived install, so clear them out first. On a fresh machine every name is absent and dnf just reports that there is nothing to do, so the command is safe to run anywhere.

sudo dnf remove -y docker docker-client docker-client-latest docker-common docker-latest docker-latest-logrotate docker-logrotate docker-selinux docker-engine-selinux docker-engine

If you installed Fedora’s moby-engine earlier, remove it as well. dnf won’t let the two coexist: with Docker CE installed, it refused moby-engine because docker-ce-cli conflicts with the docker it provides.

sudo dnf remove -y moby-engine

Podman is not on either list. Docker’s Fedora instructions do not ask you to remove it, and on our VM both runtimes worked side by side.

Add Docker’s repository

Fedora 41 and later ship dnf5, which changed the config-manager syntax. The --add-repo flag you will find in older guides and in Docker’s RHEL instructions is rejected, and dnf exits with status 2.

Unknown argument "--add-repo" for command "config-manager". Add "--help" for more information about the arguments.

Use the dnf5 subcommand instead. It downloads Docker’s repo file into /etc/yum.repos.d/, and the $releasever inside it picks the right Fedora tree on its own, so the same line works on 45, 44 and 43.

sudo dnf config-manager addrepo --from-repofile https://download.docker.com/linux/fedora/docker-ce.repo

The repo file it writes points at the stable channel with GPG checking on, which is what you want to see. If gpgcheck reads 0 on your machine, an older script wrote that file, so delete it and run the command again.

[docker-ce-stable]
name=Docker CE Stable - $basearch
baseurl=https://download.docker.com/linux/fedora/$releasever/$basearch/stable
enabled=1
gpgcheck=1
gpgkey=https://download.docker.com/linux/fedora/gpg

The dnf5 config-manager command comes from the dnf5-plugins package, which the Fedora 45 and 44 Cloud images both had (dnf-plugins-core, the dnf4 package older guides name, was not installed at all). If dnf says it doesn’t know config-manager on a trimmed image, install the plugin package and run the addrepo command again.

sudo dnf install -y dnf5-plugins

The repo file stays the same either way. Before you install, list what Docker publishes for your release, because that one query shows every package and build the install below pulls from.

dnf repoquery --repo=docker-ce-stable --latest-limit=1 --qf "%{name} %{version}-%{release}\n" 2>/dev/null | sort

Our Fedora 45 VM got seven packages back, every one built for fc45, with Buildx in the set and Docker’s new Model Runner plugin next to it:

containerd.io 2.4.2-3.fc45
docker-buildx-plugin 0.38.1-1.fc45
docker-ce 29.9.2-1.fc45
docker-ce-cli 29.9.2-1.fc45
docker-ce-rootless-extras 29.9.2-1.fc45
docker-compose-plugin 5.7.0-1.fc45
docker-model-plugin 1.2.9-1.fc45

The lab VM printed the same listing after the install below, with rpm -q confirming the four packages this page installs or adds:

dnf repoquery listing seven Docker CE packages built for fc45, including docker-buildx-plugin and docker-model-plugin

If you added a docker-ce-f44-plugins repo to this machine in October, or upgraded it from Fedora 44, it may still carry an older repo next to Docker’s own. Our Fedora 45 third-party repo guide shows how to delete the scoped repo file and move the plugin to the fc45 build. On a fresh install there is nothing to remove.

Install Docker Engine on Fedora

Install the engine, the CLI, containerd and both CLI plugins in one transaction, so all five come from the same repo snapshot. dnf resolves the whole set once and prints the transaction before it changes anything, which is your chance to check the key.

sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

On the first install dnf imports Docker’s signing key and prints its fingerprint. It must read 060A 61C5 1B55 8A7F 742B 77AA C52F EB6B 621E 9F35, which is the value on Docker’s Fedora install page; if it does not, stop, because you are not talking to Docker’s repo. This excerpt from our run shows the package set and the key.

Installing:
 containerd.io             x86_64 0:2.4.2-3.fc45  docker-ce-stable 115.6 MiB
 docker-buildx-plugin      x86_64 0:0.38.1-1.fc45 docker-ce-stable  72.4 MiB
 docker-ce                 x86_64 3:29.9.2-1.fc45 docker-ce-stable 111.2 MiB
 docker-ce-cli             x86_64 1:29.9.2-1.fc45 docker-ce-stable  36.6 MiB
 docker-compose-plugin     x86_64 0:5.7.0-1.fc45  docker-ce-stable  32.6 MiB
Installing dependencies:
 iptables-legacy           x86_64 0:1.8.13-3.fc45 fedora            99.6 KiB
 iptables-legacy-libs      x86_64 0:1.8.13-3.fc45 fedora            90.0 KiB
 libcgroup                 x86_64 0:3.0-11.fc45   fedora           157.6 KiB
Installing weak dependencies:
 docker-ce-rootless-extras x86_64 0:29.9.2-1.fc45 docker-ce-stable  20.2 MiB

Transaction Summary:
 Installing:         9 packages

Total size of inbound packages is 102 MiB. Need to download 102 MiB.
After this operation, 389 MiB extra will be used (install 389 MiB, remove 0 B).
...
Importing OpenPGP key 0x621E9F35:
 UserID     : "Docker Release (CE rpm) <[email protected]>"
 Fingerprint: 060A61C51B558A7F742B77AAC52FEB6B621E9F35
 From       : https://download.docker.com/linux/fedora/gpg
The key was successfully imported.

Nine packages took 21 seconds on our VM. Two details matter later: container-selinux was already installed because Podman’s containers-common requires it, and the weak dependency docker-ce-rootless-extras is what makes rootless mode possible, so do not exclude weak dependencies on a box where you might want it.

Start Docker and verify the install

Docker’s RPMs install the service disabled and stopped, so nothing listens on the Docker socket until you start it. Enable it now and at boot.

sudo systemctl enable --now docker

Run the hello-world image next, because it tests the whole chain in one go: the CLI reaches the daemon, the daemon pulls from Docker Hub and containerd starts a container. When one link breaks, the error tells you which one.

sudo docker run --rm hello-world

A healthy install prints the greeting after the pull (the excerpt below drops the layer progress lines and the closing help text). “Cannot connect to the Docker daemon” means the service is not running, so read systemctl status docker; a pull timeout means the daemon is up but can’t reach Docker Hub, which points at DNS or a proxy.

Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
4f55086f7dd0: Pull complete
Digest: sha256:c567b231f65614688711a75f31518a064258a51d442117a3e771a3a8fdba78ef
Status: Downloaded newer image for hello-world:latest

Hello from Docker!
This message shows that your installation appears to be working correctly.

Then confirm the versions and the storage setup. The engine and client should match, and the Compose plugin answers to docker compose with a space:

sudo docker version
sudo docker compose version
sudo docker info | grep -E 'Storage Driver|driver-type|Cgroup Version|Operating System|Firewall Backend'

On our box the engine reported containerd’s overlayfs snapshotter and cgroup v2, which is the default on Fedora and needs no tuning. The firewall backend line read iptables straight after install and only switched to iptables+firewalld once firewalld was running and Docker had restarted after it, which the firewall section below explains. This is the state at the end of the lab, with the Buildx check from the next step added:

Docker Engine 29.9.2, Compose 5.7.0 and Buildx 0.38.1 versions with the iptables+firewalld backend on Fedora 45

If the storage driver line shows overlay2 instead, the host was upgraded from a Docker release older than 29.0. Docker’s containerd image store docs say upgraded installs keep the legacy graph driver until you switch to the containerd image store yourself, so nothing is broken.

Buildx is the last plugin to check, and a version string alone proves little, so build something small with it. This Dockerfile starts from Fedora’s minimal 45 image and writes one file, which is enough to exercise BuildKit, the registry pull and the containerd image store in one pass.

mkdir -p ~/bx
printf 'FROM registry.fedoraproject.org/fedora-minimal:45\nRUN echo built-on-45 > /built\nCMD ["cat","/built"]\n' > ~/bx/Dockerfile
sudo docker buildx version
sudo docker buildx build -t bx-test ~/bx
sudo docker run --rm bx-test

Buildx reported github.com/docker/buildx v0.38.1, the build finished in 10 seconds including the base image pull, and the container printed built-on-45. Remove the test image with sudo docker rmi bx-test.

docker buildx build of a fedora-minimal 45 image that prints built-on-45 when run

Keep Fedora’s own docker-buildx package off this host. It needs Fedora’s docker-cli, which conflicts with docker-ce-cli, so dnf refuses the transaction unless you let it remove the Docker CE CLI.

Add the Docker Model Runner plugin

Docker now ships docker-model-plugin in the Fedora 45 tree, which adds a docker model command for pulling and running LLMs as OCI artifacts. It isn’t part of the documented install set, and you can skip it on any host that won’t serve models. Install it on its own when you want it:

sudo dnf install -y docker-model-plugin
sudo docker model version

The plugin is a 7.7 MiB download, and docker model version printed client v1.2.9 with the server “not reachable”, because no runner exists yet. The first docker model command that needs the runner creates one without asking. On our VM a plain status check pulled the docker/model-runner image (356 MB to download, 1.37 GB on disk), then created a model volume and started a container:

sudo docker model status

After the pull, the command reports which inference backends the runner has. On a VM with no GPU only llama.cpp was running:

Successfully pulled docker/model-runner:latest
Creating model storage volume docker-model-runner-models...
Starting model runner container docker-model-runner...
Docker Model Runner is running
BACKEND    STATUS         DETAILS
llama.cpp  Running        llama.cpp 72874f559
diffusers  Not Installed
mlx        Not Installed  only supported on Apple Silicon
sglang     Not Installed  package not installed
vllm       Not Installed  binary not found

A second status call returns at once, because the runner already exists. This is that call on the lab VM, with the runner’s ports and its image size:

docker model status showing the llama.cpp backend running and the model runner container on port 12434 on Fedora 45

The runner publishes its API on port 12434, bound to 127.0.0.1 and to the docker0 address 172.17.0.1, so it is not exposed to the LAN the way a bare -p port is (the firewalld section explains why that matters). If you only wanted to look, remove the runner and its image with sudo docker model uninstall-runner --images. That leaves the docker-model-runner-models volume behind, so delete it with sudo docker volume rm docker-model-runner-models as well.

Run Docker without sudo

Only root can talk to /var/run/docker.sock by default, so a plain docker ps as your user fails with “permission denied while trying to connect to the docker API at unix:///var/run/docker.sock”. Adding yourself to the docker group fixes it, but understand what you are granting: anyone in that group can start a container that mounts / from the host, so it’s root-equivalent. Only add accounts you would trust with sudo, and use rootless mode later on this page for everyone else.

sudo usermod -aG docker $USER
newgrp docker

newgrp applies the group to the current shell only. Log out and back in so every new session picks it up, then run docker ps without sudo; an empty table header means it worked.

Keep Podman next to Docker

Docker and Podman share nothing at runtime on Fedora: separate daemons (Podman has none), separate sockets and separate image stores. We pulled alpine with each tool and each listed only its own copy, and Podman kept running containers next to Docker and after Docker was removed. If Podman runs your toolbox containers or Quadlet units, leave it alone, since nothing here needs it gone.

The one package that does collide is podman-docker, the shim that installs a docker command backed by Podman. dnf refuses to put it next to Docker CE, and the error names the clash.

package podman-docker-5:6.1.3-1.fc45.noarch from fedora conflicts with docker-ce provided by docker-ce-3:29.9.2-1.fc45.x86_64 from docker-ce-stable

That’s the right outcome, because two different programs answering to docker on one host is a debugging trap. Use podman by its own name, and if you would rather run containers as systemd services without a daemon, the Podman Quadlet guide shows that path.

Configure the Docker daemon for Fedora

Three daemon defaults are worth changing on any Fedora host, and all three live in one file. Docker ships with its own SELinux support switched off, so docker info listed only seccomp and cgroupns under Security Options on our fresh install, and containers were not confined the way Podman confines them. The other two cost you disk space and uptime. These are the defaults the file below changes:

  • SELinux labels for containers: off by default
  • json-file logs: never rotated
  • live restore: off, so a daemon restart stops containers

Each one is a single key, so the file stays short. Before you change anything, create a small page under your home directory and read it from a container, so you have a working bind mount to compare against afterwards. With the default daemon this read succeeded on our VM.

mkdir -p ~/site && echo '<h1>served from the host</h1>' > ~/site/index.html
sudo docker run --rm -v ~/site:/data:ro alpine cat /data/index.html

The Fedora Cloud image ships only vim-minimal, which provides vi but no vim command, so install the full editor first if you want to follow the vim steps as written. Then create the daemon config.

sudo dnf install -y vim-enhanced

Open the file in the editor.

sudo vim /etc/docker/daemon.json

Add these settings. If the file already exists, merge the keys instead of replacing it, because a JSON syntax error stops the daemon from starting at all. This is the exact file our VM ran.

{
  "selinux-enabled": true,
  "log-driver": "local",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  },
  "live-restore": true
}

Check the JSON before you restart, so a typo shows up here and not as a dead daemon, then apply it. json.tool prints the file back when it parses and names the line when it does not.

python3 -m json.tool /etc/docker/daemon.json
sudo systemctl restart docker
sudo docker info --format '{{.SecurityOptions}} {{.LoggingDriver}} {{.LiveRestoreEnabled}}'

The daemon should now report SELinux among its security options, the local log driver and live restore enabled. If the restart failed instead, journalctl -u docker -n 20 names the key it rejected.

[name=seccomp,profile=builtin name=selinux name=cgroupns] local true

Containers now run in the container_t domain with their own MCS category pair, which is what makes SELinux useful here. The new log default only applies to containers created after the restart. We checked live restore the hard way: a container started 15 seconds earlier kept the same PID and start time across systemctl restart docker. Docker supports this across patch upgrades (29.9.x to 29.9.y) only, so plan a maintenance window for minor and major engine upgrades.

Error: “start of the service was attempted too often”

Docker’s unit file sets StartLimitBurst=3 and StartLimitIntervalSec=60, so systemd refuses a fourth start inside one minute. Fixing daemon.json through a few quick edit and restart rounds is enough to trip it. On our Fedora 45 VM, four systemctl restart docker calls in two seconds did, and the fourth failed with this:

Job for docker.service failed because start of the service was attempted too often.
See "systemctl status docker.service" and "journalctl -xeu docker.service" for details.
To force a start use "systemctl reset-failed docker.service"
followed by "systemctl start docker.service" again.

The daemon stays down with Result=start-limit-hit until the minute passes or you clear the counter. The unit also sets Restart=always, so with a broken daemon.json systemd’s own retries spend the three starts within seconds, and a single restart can end in this error. Live restore paid off here, because the test container kept running through the outage and showed up again as soon as the daemon was back. Clear the counter and start the daemon again.

sudo systemctl reset-failed docker.service
sudo systemctl start docker

If it fails again straight away, the counter is not the problem: a bad key in daemon.json is, and journalctl -u docker -n 20 shows which one.

Error: “Permission denied” on a bind mount

The cost of SELinux labelling shows up on the first bind mount. A directory under your home carries the user_home_t label, and a container_t process is not allowed to read it, so the read that worked before the change now fails. Run it again to see.

sudo docker run --rm -v ~/site:/data:ro alpine cat /data/index.html

The container starts, and cat is refused.

cat: can't open '/data/index.html': Permission denied

The audit log names the denial, with the container’s label as the source and the home directory’s label as the target. Search it right after the failure so the entry is still recent.

sudo ausearch -m AVC -ts recent -i

This is the line we got, trimmed to the parts that identify it. The source carries the container’s private category pair and the target carries the plain home label, which is exactly the mismatch a relabel resolves.

avc:  denied  { read } for  pid=2996 comm=cat name=index.html dev="sda3" ino=1000 scontext=system_u:system_r:container_t:s0:c61,c180 tcontext=unconfined_u:object_r:user_home_t:s0 tclass=file permissive=0

Add :Z to the volume flag and Docker relabels the directory to container_file_t with that container’s private categories, after which the read works.

sudo docker run --rm -v ~/site:/data:ro,Z alpine cat /data/index.html

The page comes back, and ls -Zd ~/site shows the new label. Use lowercase :z when several containers must share the same directory, and never relabel system paths such as /home or /etc themselves, because other services stop being able to read them.

Docker bind mount denied by SELinux then readable after the :Z relabel to container_file_t on Fedora 45

Do not switch SELinux to permissive to make this go away. The relabel is the fix, and the SELinux survival guide for Fedora covers the cases a suffix can’t solve.

Keep published ports behind firewalld

A port you publish with -p is reachable from the network even when firewalld has no rule for it. Docker rewrites the destination of published traffic in the nat table, so the packet is forwarded to the container and never meets the zone’s input rules. Docker’s docs say it also puts docker0 in its own docker zone with a forwarding policy when firewalld is running; on our host that only happened when Docker started after firewalld was fully up, as the next steps show. The exposure happens with or without that zone. Fedora Server and Workstation run firewalld by default. The Cloud image we used ships without it, so we installed and started it.

Install firewalld first, but don’t start it yet, because the default public zone allows only ssh, mdns and dhcpv6-client.

sudo dnf install -y firewalld

If sshd on this host listens on a port other than 22, new logins on that port stop working once firewalld starts. Open it now, while the service is still stopped: firewall-offline-cmd writes the saved configuration directly and is meant for exactly this window. Put your port in place of 2222. We ran this ordering on a Rocky Linux 10 lab VM and the port was open the moment firewalld came up; our Fedora host used port 22, so it skipped the step.

sudo firewall-offline-cmd --add-port=2222/tcp

Now start firewalld and check which zone holds docker0.

sudo systemctl enable --now firewalld
sudo firewall-cmd --get-zone-of-interface=docker0

On a host where Docker was already running, that query prints “no zone”, and docker info still reports a plain iptables backend. Docker only registers with firewalld when the daemon starts, so it’s a timing race. A systemctl restart docker in the same second firewalld came up missed it on our VM twice, while the next restart, once firewalld was up, registered every time. The October run of this guide hit the same thing and fixed it with a reboot. You don’t need one: wait until firewalld reports running, then restart Docker.

sudo firewall-cmd --state
sudo systemctl restart docker
sudo firewall-cmd --get-zone-of-interface=docker0
sudo docker info | grep 'Firewall Backend'

This time the journal logged “Firewalld: created docker-forwarding policy”, the zone query printed docker and the backend read iptables+firewalld. With live restore on from the daemon section, running containers stay up through that restart. The zone changes how Docker’s rules are managed, not whether a published port is exposed, as the next test shows.

docker0 shows no zone until Docker restarts after firewalld is running, then the docker zone and iptables+firewalld backend appear

We published nginx on 8080, bound a second nginx to loopback on 8082, and ran a plain Python web server on 8081 with no firewall rule for any of them. Then we requested each port from another machine on the LAN with curl -s -o /dev/null -w "%{http_code}" and printed the status for each.

remote 8080: 200
remote 8081: 000
remote 8082: 000

firewalld blocked the host service on 8081 as it should, while the Docker port on 8080 answered anyway, so the public zone’s port list says nothing about what Docker exposes. That 8081 result holds for the public zone; Fedora Workstation’s default zone opens ports 1025 to 65535, so test from another machine on your own edition. The loopback bind on 8082 was the only Docker port that stayed private. For anything that should only be reached locally or through a reverse proxy, publish it on loopback. This is the 8082 container from the test.

sudo docker run -d --name web -p 127.0.0.1:8082:80 nginx:alpine

curl localhost:8082 returned 200 on the host itself while the LAN request timed out. Remove the test container when you’re done with it:

sudo docker rm -f web

Keep a bare -p 8080:80 for services you really mean to expose. Zone and rich-rule recipes for the rest of the host are in our firewalld on Fedora guide.

Test a Compose stack

The Compose plugin is installed already, so a two-service stack is the quickest end-to-end check of networking, health checks and SELinux together. Create a project directory with a page to serve.

mkdir -p ~/web/html && cd ~/web
echo '<h1>Docker on Fedora</h1>' > ~/web/html/index.html

Open the Compose file in your editor. Compose looks for compose.yaml in the current directory before the older docker-compose.yml name, so no -f flag is needed.

vim ~/web/compose.yaml

Paste this file, which is the exact one we ran. Note the :Z on the bind mount and the loopback port, which carry over the two lessons above.

services:
  web:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"
    volumes:
      - ./html:/usr/share/nginx/html:ro,Z
    depends_on:
      cache:
        condition: service_healthy
  cache:
    image: redis:8-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 3

Validate it, start it and wait for the health checks. config --quiet prints nothing on success, so the echo tells you it passed.

docker compose config --quiet && echo config-ok
docker compose up -d --wait
curl -s localhost:8080/

Our first run pulled the Redis image (nginx was already cached from the firewall test) and returned in 17 seconds, with Redis healthy before nginx started. curl printed the page from the host directory.

<h1>Docker on Fedora</h1>

We ran the same file without ,Z against a fresh directory and nginx answered 403, for the same reason cat failed earlier: the directory kept its user_home_t label. Stop the stack with docker compose down when you’re done; named volumes survive unless you add -v. Overrides, profiles and env files are covered in the Docker Compose on Fedora guide.

Run rootless Docker

Rootless mode runs a separate Docker daemon as your own user, so a container breakout lands in an unprivileged account instead of root. Fedora makes the setup shorter than Ubuntu does, because everything it needs is already in place, including the mapping helpers Ubuntu’s cloud image lacks:

  • newuidmap and newgidmap, from shadow-utils
  • a subordinate ID range of 65,536 for the login user
  • docker-ce-rootless-extras, pulled in as a weak dependency

The third one came with the install earlier. Check the ID range for your user before you go further, because the setup tool fails without it.

grep ^$(whoami): /etc/subuid /etc/subgid

Each file should show one line ending in 65536; ours read josphat:589824:65536. Docker requires at least 65,536 subordinate IDs. If either file has no line for you, add a range with usermod and log out and back in; we didn’t need or test this, since our user already had one.

sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 $USER

The setup tool refuses to run while the system daemon answers on /var/run/docker.sock (“Aborting because rootful Docker (/var/run/docker.sock) is running and accessible”). On a machine that should run only rootless Docker, stop the system units and remove the stale socket, then run the tool as your normal user, not with sudo.

sudo systemctl disable --now docker.service docker.socket
sudo rm -f /var/run/docker.sock
dockerd-rootless-setuptool.sh install

The tool installs a systemd user unit and switches the CLI to a new context. These are its INFO lines, filtered from the daemon log it also prints; the last ones it shows ask you to add a few environment variables to ~/.bashrc for tools that don’t read Docker contexts.

[INFO] Installed docker.service successfully.
[INFO] To control docker.service, run: `systemctl --user (start|stop|restart) docker.service`
[INFO] To run docker.service on system startup, run: `sudo loginctl enable-linger josphat`
[INFO] Creating CLI context "rootless"
Successfully created context "rootless"
[INFO] Using CLI context "rootless"
Current context is now "rootless"

Without linger, systemd stops your user daemon and every container in it when your last session closes, so enable it for your account. Linger also starts the user daemon at boot, before anyone logs in.

sudo loginctl enable-linger $USER

docker info now listed rootless among the security options and no longer listed selinux. The rootless daemon reads ~/.config/docker/daemon.json, not /etc/docker/daemon.json, so the SELinux, logging and live-restore keys from earlier don’t apply to it; you can copy them into that file and run systemctl --user restart docker, though we didn’t test any of them under the rootless daemon. The nginx worker processes ran as UID 589924 on the host, an ID from our subordinate range. The limit you will hit first is ports below 1024, and it fails silently on both Fedora 45 and 44. docker run -d -p 80:80 nginx:alpine printed a container ID and docker ps showed 0.0.0.0:80->80/tcp, yet nothing listened on port 80 and curl got no answer, while the same container on 8080 returned 200 straight away. Keep rootless containers on high ports and put a reverse proxy on 80 and 443. Docker’s rootless docs list two other options, which we didn’t test: lowering net.ipv4.ip_unprivileged_port_start, which affects every user on the box, or the narrower sudo setcap cap_net_bind_service=ep $(which rootlesskit).

To go back to the system daemon, run dockerd-rootless-setuptool.sh uninstall, then docker context use default, sudo loginctl disable-linger $USER and sudo systemctl enable --now docker. The uninstall leaves your rootless images in ~/.local/share/docker, and the tool prints the command to delete them.

Upgrade, pin or remove Docker

Docker CE updates arrive through normal dnf transactions, so sudo dnf upgrade keeps the engine current along with the rest of the system. With live restore on, running containers stay up through patch updates within the same minor release. When you need to hold a validated version, dnf5 has version locking built in, so the separate dnf-plugin-versionlock package older guides install is not needed.

sudo dnf versionlock add docker-ce docker-ce-cli containerd.io
sudo dnf versionlock list

Each lock records the exact build, so the next dnf upgrade skips those packages. The excerpt shows the first of the three entries. Lock the engine, CLI and containerd together, because upgrading one without the others is how version skew starts.

# Added by 'versionlock add' command on 2026-11-03 14:58:29
Package name: docker-ce
evr = 3:29.9.2-1.fc45

Release them with sudo dnf versionlock delete docker-ce docker-ce-cli containerd.io when you’re ready to move. Removing Docker cleanly takes three steps, and live restore changes the first one. With live-restore on, stopping the daemon leaves running containers alive. Our first uninstall pass stopped Docker with two nginx containers still up, and their shims kept running after dnf had removed the packages. The rm -rf below then failed with “Device or resource busy” on their overlay mounts. So remove every container while the daemon still answers (if you installed the Model Runner, sudo docker model uninstall-runner --images takes its container and image too):

sudo docker rm -f $(sudo docker ps -aq)

Then stop the units and remove the packages. Leave docker-model-plugin off the list if you never installed it.

sudo systemctl disable --now docker.service docker.socket
sudo dnf remove -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin docker-model-plugin docker-ce-rootless-extras

dnf removed eight packages and freed 416 MiB, but it left the data behind. Most of our images were not in /var/lib/docker: that directory held 131 MB after the Buildx test, while /var/lib/containerd held 596 MB. Delete both, after you have backed up any volume you care about.

sudo rm -rf /var/lib/docker /var/lib/containerd
sudo rm -f /etc/yum.repos.d/docker-ce.repo /etc/docker/daemon.json

We ran these removal commands as written on a second pass, with a running container removed first, and all three paths were gone afterwards. Podman kept working, so you can switch back to it without reinstalling anything. The iptables alternative stays pointed at iptables-legacy, which Docker pulled in; that’s harmless, and sudo dnf remove iptables-legacy clears it if nothing else needs it. Rootless Podman keeps its images under ~/.local/share/containers, which none of the commands above touch.

Docker on Fedora 45, 44, 43 and 42 compared

We ran the whole page on a Fedora 45 VM in November, after a full run on a Fedora 44 VM in October. The service, SELinux, firewalld and rootless results matched on both, down to the silent port 80 failure; only timings and image sizes moved. On the same day as the Fedora 45 run, Fedora 44 and 43 containers resolved the same addrepo command and package list to the same Docker versions from their own trees, Buildx included. Fedora 42 is where it changes.

ItemFedora 45Fedora 44Fedora 43Fedora 42
How we checkedfull VM runfull VM run in October, container in Novembercontainer, package resolutionDocker repo listing
Docker CE in Docker’s repo29.9.229.9.229.9.229.5.3, no newer builds
Buildx plugin in Docker’s tree0.38.10.38.10.38.1not checked
Listed on Docker’s install pageyesyesyesno
Repo commandaddrepo --from-repofileaddrepo --from-repofileaddrepo --from-repofilenot checked
Fedora’s moby-engine29.9.229.9.229.9.2not checked
Fedora’s Podman6.1.35.8.95.8.9not checked
Extra dependencies on a VMiptables-legacy, libcgroupiptables-legacy, libcgroupnot checkednot checked

Fedora’s moby-engine now matches Docker’s engine on all three supported releases, which is why the plugins, not the engine, decide between them. The Fedora 44 and 43 containers pulled 55 and 57 packages instead of nine, because a container image starts without the base packages a VM already has, so judge those counts with that in mind. The row that matters is the Fedora 42 one: Docker has stopped publishing for it, so a Fedora 42 host is stuck on an engine that no longer gets fixes. Upgrade it before you install, then follow this page. If you run Docker on RHEL rebuilds too, our Rocky Linux and AlmaLinux Docker guide covers the dnf 4 side of the same repo.

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) OpenCode CLI Cheat Sheet: Commands, Sessions and Fixes (PDF) AI OpenCode CLI Cheat Sheet: Commands, Sessions and Fixes (PDF) 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 CapRover Docker Install on Debian 13: Deploy Your First App Containers CapRover Docker Install on Debian 13: Deploy Your First App Best OpenShift Books for Admins and EX280 Candidates Books Best OpenShift Books for Admins and EX280 Candidates Configure firewalld on Fedora 45 / 44: Zones, Ports, Rich Rules Fedora Configure firewalld on Fedora 45 / 44: Zones, Ports, Rich Rules

Leave a Comment

Press ESC to close