{"id":1043,"date":"2026-11-09T13:00:00","date_gmt":"2026-11-09T13:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1043"},"modified":"2026-11-09T13:00:00","modified_gmt":"2026-11-09T13:00:00","slug":"install-docker-almalinux-8","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/09\/install-docker-almalinux-8\/","title":{"rendered":"How to Install Docker on AlmaLinux 8 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Install Docker on AlmaLinux 8 with Docker Engine, Compose v2, and a safe local smoke test. In about ten minutes, you will have a working container runtime without exposing a test service to the public internet.<\/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 AlmaLinux 8 VPS with at least 2 GB of RAM and 10 GB of available disk space.<\/li>\n\n\n<li>SSH access as <code>root<\/code>, or a regular account with working <code>sudo<\/code> privileges.<\/li>\n\n\n<li>Outbound HTTPS access to Docker&#8217;s RPM repository and Docker Hub.<\/li>\n\n\n<li>No domain is required for installation or the local test. Add a domain later if you place a public application behind a TLS reverse proxy.<\/li>\n\n\n<li>A <a href=\"https:\/\/vpslake.com\/buy-vps\">VPSLake Linux VPS<\/a> if you need an AlmaLinux server with root access.<\/li>\n\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">These commands use <code>sudo<\/code> for a normal SSH account. If you are already logged in as <code>root<\/code>, omit <code>sudo<\/code>. AlmaLinux 8 uses DNF and normally uses firewalld, so do not replace the commands with Ubuntu&#8217;s <code>apt<\/code> or <code>ufw<\/code> equivalents.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm AlmaLinux 8 and update the host<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checking the release before changing repositories helps prevent installing Enterprise Linux packages for the wrong major version.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>grep -E '^(PRETTY_NAME|VERSION_ID|ID_LIKE)=' \/etc\/os-release\nuname -m\nsudo dnf -y upgrade<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The release should show AlmaLinux 8 and the architecture should normally be <code>x86_64<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PRETTY_NAME=\"AlmaLinux 8.x (Sapphire Caracal)\"\nVERSION_ID=\"8.x\"\nID_LIKE=\"rhel centos fedora\"\nx86_64<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The exact minor release changes as AlmaLinux publishes updates. If DNF reports a mirror, DNS, or GPG-signature problem, fix that before continuing.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 2: Remove packages that can conflict with Docker Engine<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">AlmaLinux may include Podman or a separate runtime, while Docker&#8217;s packages bring their own compatible containerd and CLI components.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a new VPS, remove the possible conflicting packages:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo dnf remove -y docker \\\n  docker-client \\\n  docker-client-latest \\\n  docker-common \\\n  docker-latest \\\n  docker-latest-logrotate \\\n  docker-logrotate \\\n  docker-engine \\\n  podman \\\n  runc<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">DNF may say that some packages are not installed; that is normal. Removing packages does not automatically delete Docker data in <code>\/var\/lib\/docker\/<\/code>, but do not remove Podman or <code>runc<\/code> from an existing production host until you know which workloads use them.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 3: Add Docker&#8217;s signed RPM repository<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The official repository gives DNF a maintained package source for Docker Engine, Buildx, Compose, and the container runtime.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install the repository-management plugin and add Docker&#8217;s RHEL-compatible repository:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo dnf install -y dnf-plugins-core ca-certificates\nsudo dnf config-manager --add-repo https:\/\/download.docker.com\/linux\/rhel\/docker-ce.repo\nsudo dnf makecache<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">List available Engine builds to confirm that DNF can see the repository:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>dnf list --showduplicates docker-ce | tail -n 5<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">You should see <code>docker-ce<\/code> entries from <code>docker-ce-stable<\/code>, normally with an <code>el8<\/code> suffix. Docker&#8217;s <a href=\"https:\/\/docs.docker.com\/engine\/install\/rhel\/\" target=\"_blank\" rel=\"noopener\">RHEL installation documentation<\/a> documents this repository method; AlmaLinux 8 uses the compatible Enterprise Linux package interfaces.<\/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\">This package set installs the daemon, CLI, containerd, Buildx, and the modern <code>docker compose<\/code> subcommand in one transaction.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin\nsudo systemctl enable --now docker<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Check that systemd started Docker and that the client can reach the daemon:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo systemctl is-active docker\nsudo systemctl is-enabled docker\nsudo docker version --format 'Client {{.Client.Version}} \/ Server {{.Server.Version}}'\nsudo docker compose version<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected output has an active, enabled service and version strings. The patch numbers will change over time:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>active\nenabled\nClient 29.x.y \/ Server 29.x.y\nDocker Compose version v2.x.y<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Step 5: Run a disposable container test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>hello-world<\/code> image checks the complete path from the CLI to the daemon, image registry, container creation, and process startup.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo docker run --rm hello-world<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Near the end, Docker should print:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>Hello from Docker!\nYour installation appears to be working correctly.<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The container exits on its own, and <code>--rm<\/code> removes its stopped container record. The downloaded image remains cached until you remove it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Let a regular account use Docker<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The Docker socket is root-owned by default. Adding one trusted account to the <code>docker<\/code> group avoids repeated <code>sudo<\/code>, but group membership grants root-equivalent control over the host.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this command as the account that should manage containers, replacing the placeholder with its Linux username:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo usermod -aG docker YOUR_LINUX_USER<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Log out of SSH completely and reconnect so the new group membership is loaded. Then verify access without <code>sudo<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>id -nG\ndocker info --format 'Server {{.ServerVersion}}'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The group list should include <code>docker<\/code>, and the second command should print the Docker Engine version. For a one-shell test, <code>newgrp docker<\/code> reloads group membership, but a fresh login is clearer.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Test container networking without opening a public port<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Publishing the test port on <code>127.0.0.1<\/code> checks Docker networking while keeping it unreachable from the internet.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run -d --name almalinux8-docker-test --publish 127.0.0.1:8080:80 nginx:alpine\ndocker ps --filter name=almalinux8-docker-test --format 'table {{.Names}}\\t{{.Status}}\\t{{.Ports}}'\ncurl --head http:\/\/127.0.0.1:8080<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The container should be running and curl should receive an HTTP success response:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>NAMES                   STATUS        PORTS\nalmalinux8-docker-test  Up ...        127.0.0.1:8080-&gt;80\/tcp\nHTTP\/1.1 200 OK<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Remove this disposable container when you are done:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker stop almalinux8-docker-test\ndocker rm almalinux8-docker-test<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker on AlmaLinux 8<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run one final check for service startup, daemon communication, Compose, and the storage driver:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>systemctl is-active docker\nsystemctl is-enabled docker\ndocker version --format 'Client {{.Client.Version}} \/ Server {{.Server.Version}}'\ndocker compose version\ndocker info --format 'Containers {{.Containers}} | Images {{.Images}} | Storage {{.Driver}}'<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Expected results include <code>active<\/code>, <code>enabled<\/code>, Docker client\/server versions, Compose v2, and commonly the <code>overlay2<\/code> storage driver:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>active\nenabled\nClient 29.x.y \/ Server 29.x.y\nDocker Compose version v2.x.y\nContainers 0 | Images 2 | Storage overlay2<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Docker is ready for a Compose project or an application image. For how published ports interact with Linux packet filtering, see Docker&#8217;s <a href=\"https:\/\/docs.docker.com\/engine\/network\/packet-filtering-firewalls\/\" target=\"_blank\" rel=\"noopener\">packet-filtering and firewalls guide<\/a>.<\/p>\n\n\n\n<aside class=\"vl-element vl-cta-banner\" aria-label=\"Deploy Docker on AlmaLinux\">\n\t<div class=\"vl-cta-banner__content\">\n\t\t<p class=\"vl-cta-banner__eyebrow\">AlmaLinux VPS<\/p>\n\t\t<h3 class=\"vl-cta-banner__title\">Deploy Docker on AlmaLinux<\/h3>\n\t\t<p class=\"vl-cta-banner__text\">Get an AlmaLinux VPS with root access for containers, APIs, 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 repository file was added for a different Enterprise Linux release. Check the enabled repositories and rebuild metadata:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo dnf repolist --enabled | grep -i docker\nsudo dnf makecache\ndnf list --showduplicates docker-ce<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">The list should contain Docker&#8217;s stable repository and an AlmaLinux 8-compatible build. If the repository is missing, 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 podman ... conflicts with ...<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Podman or a standalone <code>runc<\/code> package is still installed and owns files that Docker&#8217;s package set expects to manage. On a fresh host, run the removal block from Step 2 and retry the installation; on a production host, identify the dependent service before removing anything.<\/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 was opened before the account joined the <code>docker<\/code> group, or the group command was run for a different user. Reconnect, check <code>id -nG<\/code>, and retry. <code>sudo docker info<\/code> is a temporary workaround, while <code>newgrp docker<\/code> reloads the group in the current shell.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>permission denied<\/code> when a container reads a bind mount<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">SELinux is correctly preventing a container from reading a host directory without an appropriate label. Keep SELinux enabled and add the private <code>:Z<\/code> relabel option to a bind mount, for example:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run --rm -v \/srv\/app:\/app:Z YOUR_IMAGE<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Replace <code>YOUR_IMAGE<\/code> with the image you intend to run. Use <code>:z<\/code> instead when the same host directory must be shared by multiple containers, and make sure the host path has the needed Unix ownership and permissions.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Hardening<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Never expose Docker&#8217;s unauthenticated remote API on TCP port 2375. Keep administration on the Unix socket, or configure a deliberate TLS endpoint with narrow firewall rules.<\/li>\n\n\n<li>Treat membership in the <code>docker<\/code> group as administrative access. Use rootless Docker when the workload needs a stronger privilege boundary.<\/li>\n\n\n<li>Keep internal services on <code>127.0.0.1<\/code>. If an application must be public, publish only the required ports and open matching firewalld services, such as HTTP and HTTPS:<\/li>\n\n<\/ul>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo firewall-cmd --permanent --add-service=http\nsudo firewall-cmd --permanent --add-service=https\nsudo firewall-cmd --reload<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n\n<li>Do not disable SELinux to make a container work. Use the correct <code>:Z<\/code> or <code>:z<\/code> mount label and review the application&#8217;s file permissions instead.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Does Docker support AlmaLinux 8?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Docker publishes packages for RHEL 8, and AlmaLinux 8 provides compatible Enterprise Linux interfaces. The repository method above is the practical RHEL-family path; package versions change as Docker releases updates.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Is Docker or Podman better on AlmaLinux 8?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Podman is a capable daemonless option included in the Enterprise Linux ecosystem. Choose Docker when your Compose files, deployment scripts, or team workflow expect the Docker Engine daemon and CLI, and avoid running overlapping runtimes without a clear reason.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Where are Docker images and containers stored?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Docker normally keeps its data under <code>\/var\/lib\/docker<\/code>, with containerd data under <code>\/var\/lib\/containerd<\/code>. Ensure the filesystem has enough capacity before pulling large images, and inspect images, volumes, and stopped containers before pruning them.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How do I expose a container safely?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Bind internal services to <code>127.0.0.1<\/code> and place a TLS reverse proxy in front of them when they do not need direct public access. For a public service, publish only its required ports and align the firewalld policy with Docker&#8217;s packet-filtering behavior.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Does Docker support AlmaLinux 8?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Docker publishes packages for RHEL 8, and AlmaLinux 8 provides compatible Enterprise Linux interfaces. The repository method above is the practical RHEL-family path; package versions change as Docker releases updates.\"}}, {\"@type\": \"Question\", \"name\": \"Is Docker or Podman better on AlmaLinux 8?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Podman is a capable daemonless option included in the Enterprise Linux ecosystem. Choose Docker when your Compose files, deployment scripts, or team workflow expect the Docker Engine daemon and CLI, and avoid running overlapping runtimes without a clear reason.\"}}, {\"@type\": \"Question\", \"name\": \"Where are Docker images and containers stored?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Docker normally keeps its data under `\/var\/lib\/docker`, with containerd data under `\/var\/lib\/containerd`. Ensure the filesystem has enough capacity before pulling large images, and inspect images, volumes, and stopped containers before pruning them.\"}}, {\"@type\": \"Question\", \"name\": \"How do I expose a container safely?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Bind internal services to `127.0.0.1` and place a TLS reverse proxy in front of them when they do not need direct public access. For a public service, publish only its required ports and align the firewalld policy with Docker's packet-filtering behavior.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker on AlmaLinux 8 with Docker&#8217;s RPM repository, Compose v2, non-root access, a local container test, and fixes for fresh VPS deployments.","protected":false},"author":1,"featured_media":1044,"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":[94,119,118,120],"class_list":["post-1043","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-almalinux-8","tag-containers","tag-docker","tag-docker-compose","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1043","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=1043"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1043\/revisions"}],"predecessor-version":[{"id":3955,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1043\/revisions\/3955"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1044"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1043"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1043"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1043"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}