{"id":1053,"date":"2026-11-10T18:00:00","date_gmt":"2026-11-10T18:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1053"},"modified":"2026-11-10T18:00:00","modified_gmt":"2026-11-10T18:00:00","slug":"install-docker-rockylinux-8","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/10\/install-docker-rockylinux-8\/","title":{"rendered":"How to Install Docker on Rocky Linux 8 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Install Docker on Rocky Linux 8 from Docker&#8217;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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Prerequisites<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>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.<\/li>\n\n\n<li>SSH access as <code>root<\/code>, or a regular Linux account with working <code>sudo<\/code> privileges.<\/li>\n\n\n<li>Outbound HTTPS access to Docker&#8217;s package repository and Docker Hub.<\/li>\n\n\n<li>No domain is needed for installation or the local test. You need one later if a public web application will use HTTPS.<\/li>\n\n\n<li>A <a href=\"https:\/\/vpslake.com\/buy-vps\">VPSLake Linux VPS<\/a> if you need a Rocky Linux server with root access for containers.<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">These commands use <code>sudo<\/code> for a regular account. If you are logged in as <code>root<\/code>, omit <code>sudo<\/code>. Rocky Linux 8 uses DNF and firewalld, not Debian&#8217;s APT and UFW.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Check the release and update Rocky Linux<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Confirming the release before adding a repository prevents DNF from selecting packages for another Enterprise Linux generation.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash grep -E &#8216;^(PRETTY_NAME|VERSION_ID|ID_LIKE)=&#8217; \/etc\/os-release uname -m sudo dnf upgrade -y ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a fully updated host, the release should identify Rocky Linux 8.10 and the architecture will commonly be <code>x86_64<\/code>:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~text PRETTY_NAME=&#8221;Rocky Linux 8.10 (Green Obsidian)&#8221; VERSION_ID=&#8221;8.10&#8243; ID_LIKE=&#8221;rhel fedora&#8221; x86_64 ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Remove packages that conflict with Docker Engine<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Rocky Linux can have Podman, <code>runc<\/code>, or distribution Docker packages installed already. Removing unused conflicting packages lets Docker install its matching containerd runtime.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a new VPS, run:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo dnf remove -y docker \\ docker-client \\ docker-client-latest \\ docker-common \\ docker-latest \\ docker-latest-logrotate \\ docker-logrotate \\ docker-engine \\ podman \\ runc ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">DNF may say that some packages are not installed; that is expected. On an existing server, check which services use Podman or <code>runc<\/code> before removing them. This command does not erase Docker data under <code>\/var\/lib\/docker\/<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Add Docker&#8217;s RHEL-compatible repository<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Rocky Linux provides the RHEL-compatible package interfaces Docker targets, so use Docker&#8217;s RHEL repository instead of the old CentOS repository URL.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install the DNF repository plugin and certificate tools, add the signed repository, and refresh metadata:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo dnf install -y dnf-plugins-core ca-certificates curl sudo dnf config-manager &#8211;add-repo https:\/\/download.docker.com\/linux\/rhel\/docker-ce.repo sudo dnf makecache ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check that DNF can see Rocky Linux 8-compatible Engine builds before installing:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash dnf list docker-ce &#8211;showduplicates | sort -r | head -n 6 ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The list should include <code>docker-ce-stable<\/code> entries with an <code>el8<\/code> suffix. Docker documents the repository and package method in its <a href=\"https:\/\/docs.docker.com\/engine\/install\/rhel\/\" target=\"_blank\" rel=\"noopener\">RHEL installation guide<\/a>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 4: Install Docker Engine and Compose v2<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Installing the daemon, runtime, Buildx, and Compose plugin together keeps the Docker command-line tools aligned.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable &#8211;now docker ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Check both boot startup and the client-to-daemon connection:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo systemctl is-active docker sudo systemctl is-enabled docker sudo docker version &#8211;format &#8216;Client {{.Client.Version}} \/ Server {{.Server.Version}}&#8217; sudo docker compose version ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output has an active, enabled service and version strings. Docker&#8217;s patch numbers change as new releases arrive:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~text active enabled Client 29.x.y \/ Server 29.x.y Docker Compose version v2.x.y ~~~<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Run the built-in installation test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>hello-world<\/code> image checks image download, daemon communication, container creation, and process startup in one short command.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo docker run &#8211;rm hello-world ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Near the end, look for:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~text Hello from Docker! Your installation appears to be working correctly. ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The container exits after printing its message. <code>--rm<\/code> removes the stopped container record, although the downloaded image remains in the local cache.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Give one account non-root Docker access<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Docker&#8217;s Unix socket is root-owned by default. The <code>docker<\/code> group avoids repeated <code>sudo<\/code>, but every member has effectively administrative control of the VPS.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this as the regular account that should operate containers, replacing the placeholder with that account&#8217;s Linux username:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo groupadd &#8211;force docker sudo usermod -aG docker YOUR_LINUX_USER ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Log out of SSH completely and reconnect so the group membership is loaded. Then test the socket without <code>sudo<\/code>:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash id -nG docker info &#8211;format &#8216;Server {{.ServerVersion}}&#8217; ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The group list should contain <code>docker<\/code>, and the second command should print the Engine version. <code>newgrp docker<\/code> can refresh the current shell, but a new SSH session is less error-prone.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Test a container without opening a public port<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This disposable NGINX container checks Docker&#8217;s port publishing while binding only to loopback, so it needs no DNS record or firewalld rule.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash docker run -d &#8211;name rocky8-docker-check &#8211;publish 127.0.0.1:18083:80 nginx:alpine docker ps &#8211;filter name=rocky8-docker-check &#8211;format &#8216;table {{.Names}}\\t{{.Status}}\\t{{.Ports}}&#8217; curl &#8211;head &#8211;max-time 10 http:\/\/127.0.0.1:18083 ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">You should see a running container and an HTTP success response:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~text NAMES                STATUS        PORTS rocky8-docker-check  Up &#8230;        127.0.0.1:18083-&gt;80\/tcp HTTP\/1.1 200 OK ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Remove the test container after confirming the response:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash docker stop rocky8-docker-check docker rm rocky8-docker-check ~~~<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker on Rocky Linux 8<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run this final check as the Docker administrator to confirm service startup, daemon access, Compose, and local storage:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash systemctl is-active docker systemctl is-enabled docker docker version &#8211;format &#8216;Client {{.Client.Version}} \/ Server {{.Server.Version}}&#8217; docker compose version docker info &#8211;format &#8216;Containers {{.Containers}} | Images {{.Images}} | Storage {{.Driver}}&#8217; ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Successful output includes <code>active<\/code>, <code>enabled<\/code>, Docker client\/server versions, Compose v2, and commonly <code>overlay2<\/code>:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~text active enabled Client 29.x.y \/ Server 29.x.y Docker Compose version v2.x.y Containers 0 | Images 2 | Storage overlay2 ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For published-port behavior and Docker&#8217;s interaction with host filtering, read the official <a href=\"https:\/\/docs.docker.com\/engine\/network\/packet-filtering-firewalls\/\" target=\"_blank\" rel=\"noopener\">packet-filtering and firewall guidance<\/a>.<\/p>\n\n\n\n<aside class=\"vl-element vl-cta-banner\" aria-label=\"Run Docker on Rocky Linux 8\">\n\t<div class=\"vl-cta-banner__content\">\n\t\t<p class=\"vl-cta-banner__eyebrow\">Rocky Linux VPS<\/p>\n\t\t<h3 class=\"vl-cta-banner__title\">Run Docker on Rocky Linux 8<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Get a root-access Rocky Linux VPS for Docker apps, Compose projects, and development workloads.<\/p>\n\t<\/div>\n\t<a class=\"vl-cta-banner__button\" href=\"\/buy-vps\" aria-label=\"View Linux VPS plans\">View Linux VPS plans<\/a>\n<\/aside>\n\n\n\n<h2 class=\"wp-block-heading\">Troubleshooting<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\"><code>No match for argument: docker-ce<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">DNF has not loaded Docker&#8217;s repository, or the host is not on Rocky Linux 8. Check the release, repository list, and metadata:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash grep -E &#8216;^(PRETTY_NAME|VERSION_ID)=&#8217; \/etc\/os-release sudo dnf repolist &#8211;enabled | grep -i docker sudo dnf makecache dnf list docker-ce &#8211;showduplicates ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">The enabled list should include Docker&#8217;s stable repository and the package list should show an <code>el8<\/code> build. If the repository is absent, repeat the <code>dnf config-manager --add-repo<\/code> command from Step 3.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>package ... conflicts with ...<\/code> during installation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Podman, <code>runc<\/code>, or an older Docker package still owns a file required by Docker&#8217;s package set. On a fresh VPS, rerun Step 2; on a production host, identify the dependent service before removing a runtime.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>permission denied while trying to connect to the Docker daemon socket<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The SSH session started before the account joined the <code>docker<\/code> group, or <code>usermod<\/code> targeted the wrong account. Reconnect, run <code>id -nG<\/code>, and retry. <code>sudo docker info<\/code> is a temporary diagnostic, not the non-root fix.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>curl: (7) Failed to connect<\/code> or <code>bind: address already in use<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The image may still be downloading, the test container may have stopped, or another process may own port 18083. Inspect the container and listener:<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash docker ps -a &#8211;filter name=rocky8-docker-check docker logs rocky8-docker-check sudo ss -ltnp | grep &#8216;:18083&#8217; ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">If the port is occupied, remove the failed test and repeat it with <code>--publish 127.0.0.1:28083:80<\/code>, then request <code>http:\/\/127.0.0.1:28083<\/code>. A loopback binding does not need a public firewall rule.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hardening<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Keep Docker&#8217;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.<\/li>\n\n\n<li>Treat membership in the <code>docker<\/code> group as root-equivalent access. Use rootless Docker when you need a stronger privilege boundary.<\/li>\n\n\n<li>Keep internal services on <code>127.0.0.1<\/code>. For a public web service, open only HTTP and HTTPS in firewalld after confirming SSH access remains allowed:<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash sudo firewall-cmd &#8211;permanent &#8211;add-service=http sudo firewall-cmd &#8211;permanent &#8211;add-service=https sudo firewall-cmd &#8211;reload ~~~<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Keep SELinux enabled. For a private bind mount, add Docker&#8217;s <code>:Z<\/code> label instead of disabling policy enforcement:<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">~~~bash docker run &#8211;rm -v \/srv\/app:\/app:Z YOUR_IMAGE ~~~<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Replace <code>YOUR_IMAGE<\/code> with the image you plan to run. Use <code>:z<\/code> only when the same host directory must be shared by multiple containers.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Is Rocky Linux 8 still supported?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Why does this guide use the RHEL repository?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Docker&#8217;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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Can Docker and Podman run on the same Rocky server?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">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.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Do I need to open port 18083 in firewalld?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">No. The test publishes to <code>127.0.0.1<\/code>, 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.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Is Rocky Linux 8 still supported?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"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.\"}}, {\"@type\": \"Question\", \"name\": \"Why does this guide use the RHEL repository?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"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.\"}}, {\"@type\": \"Question\", \"name\": \"Can Docker and Podman run on the same Rocky server?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"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.\"}}, {\"@type\": \"Question\", \"name\": \"Do I need to open port 18083 in firewalld?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"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.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker on Rocky Linux 8 with the official RPM repository, Compose v2, SELinux-safe mounts, non-root access, and a verified container.","protected":false},"author":1,"featured_media":1054,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"csco_singular_sidebar":"","csco_page_header_type":"","csco_page_load_nextpost":"","footnotes":""},"categories":[5,9],"tags":[119,118,120,105],"class_list":["post-1053","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-containers","tag-docker","tag-docker-compose","tag-rocky-linux-8","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1053","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/comments?post=1053"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1053\/revisions"}],"predecessor-version":[{"id":3975,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1053\/revisions\/3975"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1054"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1053"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1053"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1053"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}