Skip to content

Docker on a RHEL-family Workstation

These are the steps and the two hard-won fixes for standing up a development machine on the RHEL family. They were originally written for CentOS Linux 8, which reached end of life on 31 December 2021; CentOS Linux 7 followed on 30 June 2024. Neither receives updates. The replacements are RHEL 9, Rocky Linux 9, AlmaLinux 9 or CentOS Stream 9, and the commands below assume one of those.

Red Hat ships podman-docker, which installs a docker shim at /usr/bin/docker and therefore conflicts with docker-ce-cli. Remove it and anything left from an older Docker before adding Docker’s own repository:

Terminal window
sudo dnf remove -y podman-docker docker docker-client docker-client-latest \
docker-common docker-latest docker-latest-logrotate docker-logrotate \
docker-selinux docker-engine-selinux docker-engine
sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
sudo systemctl enable --now docker

docker-compose-plugin provides Compose v2 as the docker compose subcommand. The standalone docker-compose v1 binary that older guides download from GitHub releases stopped receiving updates in 2023 and should not be installed.

Add yourself to the docker group so the daemon socket is usable without sudo, then log out and back in for the group to take effect:

Terminal window
sudo usermod -aG docker $USER

Membership of the docker group is equivalent to root on the host: anyone in it can start a privileged container that mounts the root filesystem. Treat it as an administrative privilege, not a convenience.

Check the install:

Terminal window
docker version
docker compose version
docker run --rm hello-world

Install the packaged LTS build; dnf search openjdk lists what the release carries.

Terminal window
sudo dnf install java-21-openjdk-devel

Where several JDKs are installed, switch the default with alternatives.

Terminal window
sudo dnf install git

Follow connecting to GitHub with SSH to generate a key and register it. On a network that blocks outbound port 22, GitHub serves SSH on 443 as well — put this in ~/.ssh/config:

Host github.com
Hostname ssh.github.com
Port 443
User git
Terminal window
chmod 600 ~/.ssh/config
ssh -T git@github.com
  • Gradle — install the current release from gradle.org/install, or let each repository’s Gradle wrapper (./gradlew) pick its own version, which is preferable because the build then pins its own toolchain.
  • Node.jsdnf module list nodejs shows the streams the release offers; install the one you need with sudo dnf module install nodejs:<stream>.
  • Kubernetes tooling — follow Install Tools for kubectl, and kind or minikube for a local cluster.
  • IntelliJ IDEA — install through the JetBrains Toolbox App so updates are managed for you, rather than unpacking a fixed build into /opt.

408 Request Time-out signing in or pulling an image

Section titled “408 Request Time-out signing in or pulling an image”

Caused here by the MTU on both the wired and wireless interfaces being left at AUTO. Lowering it to 900 fixed it. Three ways to do that, in increasing order of permanence:

Temporarily, for this boot. Useful to confirm the MTU really is the cause before changing anything persistent:

Terminal window
ip link show
sudo ip link set wlp2s0 mtu 900
sudo ip link set enp0s31f6 mtu 900

Through the NetworkManager text UI. nmtui edits the stored connection profile, so the change survives a reboot:

Terminal window
sudo nmtui
sudo nmcli connection down [NIC_NAME]
sudo nmcli connection up [NIC_NAME]

By editing the connection profile directly. Equivalent to the above, and scriptable:

Terminal window
sudo nmcli connection modify [NIC_NAME] 802-3-ethernet.mtu 900
sudo nmcli connection down [NIC_NAME]
sudo nmcli connection up [NIC_NAME]
ip addr show [NIC_NAME]

Further reading: Docker connection refused and setting the MTU for a network interface.

This is a host firewall problem, not a Docker one. Work through it in order:

  1. Confirm the port is listening at all: ss -ltnp | grep <port>.
  2. Confirm it is reachable from the host: nc -vz localhost <port>.
  3. Watch the target container accept — or not accept — the connection: docker logs -f containerA.
  4. If the host can connect and the other container cannot, stop firewalld briefly to confirm the firewall is responsible, then start it again.
  5. Fix it properly by enabling masquerading on the zone Docker’s bridge sits in, which is what lets container traffic route out through the host:
Terminal window
sudo firewall-cmd --zone=public --add-masquerade --permanent
sudo firewall-cmd --reload

Note --permanent, spelled in full — without it the rule is lost at the next reload, and a misspelling makes the command fail outright.