How to Install Docker on Rocky Linux 9 (Step-by-Step)

Install Docker on Rocky Linux 9 from the official RPM repository, add Compose v2, run a container test, and fix common VPS errors on a fresh server.
A polished 3D Rocky Linux server scene with glowing containers, blue light trails, and a green accent on a midnight-blue background.

Install Docker on Rocky Linux 9 from Docker’s RPM repository and leave the VPS ready for Compose projects. In about ten minutes, you can verify the daemon, grant one account Docker access, and test container networking without publishing a port to the internet.

Prerequisites

  • A 64-bit Rocky Linux 9 VPS, preferably x86_64, with at least 2 GB of RAM and 10 GB of free disk space.
  • SSH access as root, or a regular account that can run commands with sudo.
  • Outbound HTTPS access to Docker’s package repository and Docker Hub.
  • No domain is needed for the installation or local test. Use a domain when you later put a public web application behind HTTPS.
  • A VPSLake Linux VPS if you need a Rocky Linux server with root access.

Rocky Linux 9 uses DNF and normally runs firewalld. The commands below use sudo; omit it when you are already in a root shell.

Step 1: Confirm Rocky Linux 9 and update the host

Checking the release before changing repositories prevents an Enterprise Linux package mismatch.

grep -E '^(PRETTY_NAME|VERSION_ID|ID_LIKE)=' /etc/os-release
uname -m
sudo dnf upgrade -y
sudo dnf install -y ca-certificates curl dnf-plugins-core

The first command should identify Rocky Linux 9, and the architecture should normally be x86_64:

PRETTY_NAME="Rocky Linux 9.x (Blue Onyx)"
VERSION_ID="9.x"
ID_LIKE="rhel centos fedora"
x86_64

Minor-release and package versions change over time. Fix any DNF mirror, DNS, or GPG-signature error before proceeding.

Step 2: Clear conflicting container packages

The official Docker packages include their expected containerd and runtime components, so remove distribution packages that can claim the same files on a fresh server.

sudo dnf remove -y docker \
  docker-client \
  docker-client-latest \
  docker-common \
  docker-latest \
  docker-latest-logrotate \
  docker-logrotate \
  docker-engine \
  podman \
  runc

DNF may report that some names are not installed; that is fine. On a server already running Podman, inspect its services before removing it. Uninstalling the packages does not automatically erase Docker data stored in /var/lib/docker/.

Step 3: Add Docker’s Rocky-compatible RPM repository

Rocky Linux 9 is compatible with the RHEL 9 package interface, so Docker’s RHEL repository provides the maintained Engine packages without an old CentOS-specific workaround.

sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
sudo dnf makecache
dnf list docker-ce --showduplicates | sort -r | head -n 6

The package list should show docker-ce entries from docker-ce-stable with an el9 suffix. For the repository and package names, see Docker’s RHEL installation guide.

Step 4: Install Engine, Buildx, and Compose

Installing the daemon, CLI, runtime, image builder, and Compose plugin together keeps the Docker toolchain aligned.

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

If DNF asks to accept Docker’s GPG key, confirm that its fingerprint matches the one in Docker’s official instructions before accepting it. Then check the service and client tools:

sudo systemctl is-active docker
sudo systemctl is-enabled docker
sudo docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
sudo docker compose version

Expected output shows an active, enabled service. Exact versions vary as Docker publishes updates:

active
enabled
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y

Step 5: Run the built-in Docker smoke test

The hello-world image checks registry access, the daemon socket, image extraction, container creation, and process startup in one command.

sudo docker run --rm hello-world

Near the end, you should see:

Hello from Docker!
Your installation appears to be working correctly.

The container exits after printing the message. --rm removes its stopped container record, but the downloaded image remains cached.

Step 6: Allow one trusted account to use Docker

The Docker socket is root-owned by default; adding one administrator to the docker group removes repeated sudo prompts but grants that account effectively root-level control of the host.

Run this as the account that should manage containers, replacing YOUR_ADMIN_USER with its Linux login name:

sudo groupadd --force docker
sudo usermod -aG docker YOUR_ADMIN_USER

Log out of SSH and reconnect as YOUR_ADMIN_USER so the new group is loaded. Then test the socket without sudo:

id -nG
docker info --format 'Server {{.ServerVersion}}'

The group list should contain docker, and the second command should print the Engine version. newgrp docker can reload group membership in the current terminal, but a new SSH session is less error-prone.

Step 7: Test a local web container

A loopback-only port mapping proves Docker networking works while keeping the disposable NGINX service inaccessible from the public network.

docker run -d --name rocky9-docker-check --publish 127.0.0.1:18090:80 nginx:alpine
docker ps --filter name=rocky9-docker-check --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
curl --head --max-time 10 http://127.0.0.1:18090

The container should be running and curl should return an HTTP success response:

NAMES               STATUS      PORTS
rocky9-docker-check Up ...      127.0.0.1:18090->80/tcp
HTTP/1.1 200 OK

Remove this disposable container after the check:

docker stop rocky9-docker-check
docker rm rocky9-docker-check

Verify Docker on Rocky Linux 9

Run this final check as the Docker administrator to confirm startup at boot, daemon communication, Compose, and the storage driver:

systemctl is-active docker
systemctl is-enabled docker
docker version --format 'Client {{.Client.Version}} / Server {{.Server.Version}}'
docker compose version
docker info --format 'Containers {{.Containers}} | Images {{.Images}} | Storage {{.Driver}}'

Successful output includes active, enabled, client and server versions, Compose v2, and commonly overlay2:

active
enabled
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y
Containers 0 | Images 2 | Storage overlay2

Docker automatically creates firewall rules for bridge networking. Read its packet-filtering and firewalls documentation before changing Docker’s rules or publishing a production service.

Troubleshooting

No match for argument: docker-ce

DNF cannot see Docker’s repository, or the host is not Rocky Linux 9. Confirm the release and repository metadata, then retry the repository command from Step 3:

grep -E '^(PRETTY_NAME|VERSION_ID)=' /etc/os-release
sudo dnf repolist --enabled | grep -i docker
sudo dnf makecache
dnf list docker-ce --showduplicates

The list should include Docker’s stable repository and an el9 candidate. Avoid switching to a random CentOS repository URL.

package ... conflicts with ... during installation

Podman, runc, or a distribution Docker package is still installed and owns a file required by the official package set. On a new VPS, rerun Step 2; on an active host, identify the service that depends on the package before removing it.

permission denied while trying to connect to the Docker daemon socket

The SSH session predates the group change, or usermod targeted a different account. Reconnect as YOUR_ADMIN_USER, run id -nG, and retry. Use sudo docker info only as a temporary diagnostic while correcting the group membership.

curl: (7) Failed to connect to 127.0.0.1 port 18090

The image may still be downloading, the container may have exited, or another local process may own the port. Inspect the container and listener:

docker ps -a --filter name=rocky9-docker-check
docker logs rocky9-docker-check
sudo ss -ltnp | grep ':18090'

If the port is busy, remove the failed test and repeat it with --publish 127.0.0.1:28090:80, then request http://127.0.0.1:28090. A loopback binding does not need a public firewalld rule.

Hardening

  • Keep the Docker API on its Unix socket. Never expose an unauthenticated daemon on TCP port 2375; remote access requires deliberate TLS configuration and narrow source restrictions.
  • Leave SELinux enabled. For a bind mount, use a private :Z label such as -v /srv/app:/app:Z so the container receives the correct SELinux context.
  • Keep internal services bound to 127.0.0.1 until a TLS reverse proxy is ready. For a public web service, open only HTTP and HTTPS in firewalld:
  sudo firewall-cmd --permanent --add-service=http
  sudo firewall-cmd --permanent --add-service=https
  sudo firewall-cmd --reload
  • Docker and firewalld cooperate through Docker-managed zones and forwarding rules. Do not disable Docker’s iptables/nftables handling to force a simpler-looking firewall configuration; bridge networking can stop working.

FAQ

Does Docker support Rocky Linux 9?

Docker publishes RHEL 9 packages, and Rocky Linux 9 provides the compatible Enterprise Linux interfaces used by this repository method. The el9 build and exact Engine version change as Docker releases updates.

Why use Docker’s repository instead of Rocky’s packages?

The Docker repository supplies the Docker Engine package family together with containerd, Buildx, and the Compose plugin. That keeps the command names and release cadence consistent with Docker’s own Linux documentation.

Can Podman and Docker run on the same Rocky VPS?

They can coexist in some configurations, but both add container tooling and may compete for runtime packages. Use one deliberately; if an existing Podman workload matters, inventory it before following the removal step.

Do I need to open port 18090 in firewalld?

No. The test binds to loopback, so only processes on the VPS can reach it. For a real public container, publish only the required ports, keep SSH restricted, and use HTTPS for web traffic.

Samrat Ghosh
Written by

Samrat Ghosh

Founder & Infrastructure Engineer

Samrat Ghosh is the founder of VPSLake, where he builds and runs the remote desktop and VPS hosting infrastructure the company is built on. He writes hands-on guides about RDP, Windows Server, VPS management and secure remote access - the practical documentation he wishes had…

Previous Article

Monthly vs Yearly VPS Billing: Which Should You Choose?

Next Article

Busy Software on Cloud: Run BUSY on Your Own RDP

Write a Comment

Leave a Comment

Your email address will not be published. Required fields are marked *

Subscribe to our Newsletter

Subscribe to our email newsletter to get the latest posts delivered right to your email.
Pure inspiration, zero spam ✨