Install Docker on Rocky Linux 8 from Docker’s official RPM repository, then run a real container to verify the daemon and networking. The setup takes about ten minutes on a fresh VPS and keeps the test service private to the server.
Prerequisites
- A 64-bit Rocky Linux 8 VPS updated to the current 8.10 minor release, with at least 2 GB of RAM and 10 GB of free disk space.
- SSH access as
root, or a regular Linux account with workingsudoprivileges. - Outbound HTTPS access to Docker’s package repository and Docker Hub.
- No domain is needed for installation or the local test. You need one later if a public web application will use HTTPS.
- A VPSLake Linux VPS if you need a Rocky Linux server with root access for containers.
These commands use sudo for a regular account. If you are logged in as root, omit sudo. Rocky Linux 8 uses DNF and firewalld, not Debian’s APT and UFW.
Step 1: Check the release and update Rocky Linux
Confirming the release before adding a repository prevents DNF from selecting packages for another Enterprise Linux generation.
~~~bash grep -E ‘^(PRETTY_NAME|VERSION_ID|ID_LIKE)=’ /etc/os-release uname -m sudo dnf upgrade -y ~~~
On a fully updated host, the release should identify Rocky Linux 8.10 and the architecture will commonly be x86_64:
~~~text PRETTY_NAME=”Rocky Linux 8.10 (Green Obsidian)” VERSION_ID=”8.10″ ID_LIKE=”rhel fedora” x86_64 ~~~
If DNF reports a mirror, DNS, or GPG-signature problem, resolve it before installing Docker. Rocky Linux 8 remains in security maintenance, but staying on the latest 8.x minor release matters.
Step 2: Remove packages that conflict with Docker Engine
Rocky Linux can have Podman, runc, or distribution Docker packages installed already. Removing unused conflicting packages lets Docker install its matching containerd runtime.
On a new VPS, run:
~~~bash 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 say that some packages are not installed; that is expected. On an existing server, check which services use Podman or runc before removing them. This command does not erase Docker data under /var/lib/docker/.
Step 3: Add Docker’s RHEL-compatible repository
Rocky Linux provides the RHEL-compatible package interfaces Docker targets, so use Docker’s RHEL repository instead of the old CentOS repository URL.
Install the DNF repository plugin and certificate tools, add the signed repository, and refresh metadata:
~~~bash sudo dnf install -y dnf-plugins-core ca-certificates curl sudo dnf config-manager –add-repo https://download.docker.com/linux/rhel/docker-ce.repo sudo dnf makecache ~~~
Check that DNF can see Rocky Linux 8-compatible Engine builds before installing:
~~~bash dnf list docker-ce –showduplicates | sort -r | head -n 6 ~~~
The list should include docker-ce-stable entries with an el8 suffix. Docker documents the repository and package method in its RHEL installation guide.
Step 4: Install Docker Engine and Compose v2
Installing the daemon, runtime, Buildx, and Compose plugin together keeps the Docker command-line tools aligned.
~~~bash sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable –now docker ~~~
Check both boot startup and the client-to-daemon connection:
~~~bash 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 has an active, enabled service and version strings. Docker’s patch numbers change as new releases arrive:
~~~text active enabled Client 29.x.y / Server 29.x.y Docker Compose version v2.x.y ~~~
Step 5: Run the built-in installation test
The hello-world image checks image download, daemon communication, container creation, and process startup in one short command.
~~~bash sudo docker run –rm hello-world ~~~
Near the end, look for:
~~~text Hello from Docker! Your installation appears to be working correctly. ~~~
The container exits after printing its message. --rm removes the stopped container record, although the downloaded image remains in the local cache.
Step 6: Give one account non-root Docker access
Docker’s Unix socket is root-owned by default. The docker group avoids repeated sudo, but every member has effectively administrative control of the VPS.
Run this as the regular account that should operate containers, replacing the placeholder with that account’s Linux username:
~~~bash sudo groupadd –force docker sudo usermod -aG docker YOUR_LINUX_USER ~~~
Log out of SSH completely and reconnect so the group membership is loaded. Then test the socket without sudo:
~~~bash 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 refresh the current shell, but a new SSH session is less error-prone.
Step 7: Test a container without opening a public port
This disposable NGINX container checks Docker’s port publishing while binding only to loopback, so it needs no DNS record or firewalld rule.
~~~bash docker run -d –name rocky8-docker-check –publish 127.0.0.1:18083:80 nginx:alpine docker ps –filter name=rocky8-docker-check –format ‘table {{.Names}}\t{{.Status}}\t{{.Ports}}’ curl –head –max-time 10 http://127.0.0.1:18083 ~~~
You should see a running container and an HTTP success response:
~~~text NAMES STATUS PORTS rocky8-docker-check Up … 127.0.0.1:18083->80/tcp HTTP/1.1 200 OK ~~~
Remove the test container after confirming the response:
~~~bash docker stop rocky8-docker-check docker rm rocky8-docker-check ~~~
Verify Docker on Rocky Linux 8
Run this final check as the Docker administrator to confirm service startup, daemon access, Compose, and local storage:
~~~bash 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, Docker client/server versions, Compose v2, and commonly overlay2:
~~~text active enabled Client 29.x.y / Server 29.x.y Docker Compose version v2.x.y Containers 0 | Images 2 | Storage overlay2 ~~~
For published-port behavior and Docker’s interaction with host filtering, read the official packet-filtering and firewall guidance.
Troubleshooting
No match for argument: docker-ce
DNF has not loaded Docker’s repository, or the host is not on Rocky Linux 8. Check the release, repository list, and metadata:
~~~bash 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 enabled list should include Docker’s stable repository and the package list should show an el8 build. If the repository is absent, repeat the dnf config-manager --add-repo command from Step 3.
package ... conflicts with ... during installation
Podman, runc, or an older Docker package still owns a file required by Docker’s package set. On a fresh VPS, rerun Step 2; on a production host, identify the dependent service before removing a runtime.
permission denied while trying to connect to the Docker daemon socket
The SSH session started before the account joined the docker group, or usermod targeted the wrong account. Reconnect, run id -nG, and retry. sudo docker info is a temporary diagnostic, not the non-root fix.
curl: (7) Failed to connect or bind: address already in use
The image may still be downloading, the test container may have stopped, or another process may own port 18083. Inspect the container and listener:
~~~bash docker ps -a –filter name=rocky8-docker-check docker logs rocky8-docker-check sudo ss -ltnp | grep ‘:18083’ ~~~
If the port is occupied, remove the failed test and repeat it with --publish 127.0.0.1:28083:80, then request http://127.0.0.1:28083. A loopback binding does not need a public firewall rule.
Hardening
- Keep Docker’s API on its Unix socket. Never expose an unauthenticated Docker API on TCP port 2375; remote administration requires deliberate TLS configuration and narrow source-IP rules.
- Treat membership in the
dockergroup as root-equivalent access. Use rootless Docker when you need a stronger privilege boundary. - Keep internal services on
127.0.0.1. For a public web service, open only HTTP and HTTPS in firewalld after confirming SSH access remains allowed:
~~~bash sudo firewall-cmd –permanent –add-service=http sudo firewall-cmd –permanent –add-service=https sudo firewall-cmd –reload ~~~
- Keep SELinux enabled. For a private bind mount, add Docker’s
:Zlabel instead of disabling policy enforcement:
~~~bash docker run –rm -v /srv/app:/app:Z YOUR_IMAGE ~~~
Replace YOUR_IMAGE with the image you plan to run. Use :z only when the same host directory must be shared by multiple containers.
FAQ
Is Rocky Linux 8 still supported?
Rocky Linux 8 receives security maintenance through May 31, 2029. Keep the host on the latest 8.10 packages and plan a major-version migration before that maintenance window ends.
Why does this guide use the RHEL repository?
Docker’s official repository instructions target RHEL, while Rocky Linux supplies compatible Enterprise Linux package interfaces. The RHEL URL is therefore the current supported package path for this installation; the older CentOS URL in many legacy tutorials is not needed.
Can Docker and Podman run on the same Rocky server?
They can coexist in some configurations, but their runtimes and command names make a small VPS harder to reason about. Remove unused conflicting packages on a fresh host, and migrate active Podman workloads deliberately instead of uninstalling their runtime blindly.
Do I need to open port 18083 in firewalld?
No. The test publishes to 127.0.0.1, so only processes on the VPS can reach it. When you publish a real application publicly, open only its required port and use a domain with TLS for web traffic.