{"id":1041,"date":"2026-11-09T09:00:00","date_gmt":"2026-11-09T09:00:00","guid":{"rendered":"https:\/\/vpslake.com\/blog\/?p=1041"},"modified":"2026-11-09T09:00:00","modified_gmt":"2026-11-09T09:00:00","slug":"install-docker-almalinux-10","status":"publish","type":"post","link":"https:\/\/vpslake.com\/blog\/2026\/11\/09\/install-docker-almalinux-10\/","title":{"rendered":"How to Install Docker on AlmaLinux 10 (Step-by-Step)"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">Install Docker on AlmaLinux 10 with Docker Engine, Compose v2, and a non-root administration setup. In about ten minutes, you will run a verified container and a local web test without exposing a new 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 10 VPS with a current package index, 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 normal 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 this installation or the local smoke test. Use a domain later if you put 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\">The commands use <code>sudo<\/code> for a normal SSH user. If you are logged in as <code>root<\/code>, remove <code>sudo<\/code>. AlmaLinux uses DNF and firewalld; do not substitute Ubuntu&#8217;s <code>apt<\/code> or <code>ufw<\/code> commands.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 1: Confirm AlmaLinux 10 and update the host<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Checking the release first prevents you from adding a repository for a different Enterprise Linux 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 identify AlmaLinux 10 and the architecture should normally be <code>x86_64<\/code>:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>PRETTY_NAME=\"AlmaLinux 10.x (Cerulean Lion)\"\nVERSION_ID=\"10.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. Resolve any DNF mirror, DNS, or signature error before installing Docker.<\/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\">Distribution-provided Docker packages, Podman, and a separately installed runtime can conflict with Docker&#8217;s bundled containerd and networking components.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">On a new VPS, remove the possible conflicting packages with DNF:<\/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\">If DNF says a package is not installed, that is expected. Removing these packages does not automatically delete Docker data under <code>\/var\/lib\/docker\/<\/code>, but do not remove a package from a production host until you have checked which workloads depend on it.<\/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 repository supplies Docker Engine, its CLI, containerd, Buildx, and the maintained Compose plugin as a compatible package set.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Install the repository-management tools and add Docker&#8217;s RHEL-compatible repository for AlmaLinux 10:<\/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\">Confirm that DNF can see an Engine candidate before installing anything:<\/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 one or more <code>docker-ce<\/code> entries from <code>download.docker.com<\/code>. Docker publishes new patch versions regularly, so the version and release date will differ from this example.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">For the repository prerequisites and package names, see Docker&#8217;s <a href=\"https:\/\/docs.docker.com\/engine\/install\/rhel\/\" target=\"_blank\" rel=\"noopener\">RHEL installation documentation<\/a>. AlmaLinux&#8217;s compatible Enterprise Linux package interfaces let DNF use this package path.<\/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\">These packages install the daemon, CLI, runtime, Buildx builder, and the <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 the service and command-line tools immediately:<\/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 matching client\/server versions. Patch numbers will change:<\/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 Docker&#8217;s installation test<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">The <code>hello-world<\/code> image proves that the CLI can reach the daemon, pull an image, create a container, and run it successfully.<\/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\">Look for this confirmation near the end of the output:<\/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 after printing the message, and <code>--rm<\/code> removes its stopped container record. The small image remains cached until you prune it.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 6: Allow a trusted account to run Docker<\/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 removes repeated <code>sudo<\/code> prompts, but membership is effectively administrative access to the host.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\">Run this block from the normal sudo-enabled account that should manage containers, not from a root shell:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>sudo groupadd --force docker\nsudo usermod -aG docker \"$USER\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Log out of SSH completely and reconnect so the new group is loaded. Then check 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 Engine version. Until you reconnect, use <code>newgrp docker<\/code> or keep using <code>sudo docker<\/code>.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Step 7: Test a local web container<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">This test checks Docker networking while binding the host port to loopback, so a random internet client cannot reach the container.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker run -d --name almalinux-docker-test --publish 127.0.0.1:8080:80 nginx:alpine\ndocker ps --filter name=almalinux-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\nalmalinux-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 the disposable test when you are finished:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker stop almalinux-docker-test\ndocker rm almalinux-docker-test<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Verify Docker on AlmaLinux 10<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Run this final check to confirm service startup, daemon communication, Compose, and the container runtime:<\/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, a Compose v2 version, and a storage driver such as <code>overlay2<\/code>:<\/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 application image. For firewall behavior and published ports, read 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 was added for a different Enterprise Linux release. Inspect the repo file and refresh 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 enabled list should contain Docker&#8217;s repository and the package list should show an <code>el10<\/code> build. If the repo file is missing, run the <code>dnf config-manager --add-repo<\/code> command from Step 3 again.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>package podman ... conflicts with ...<\/code> during installation<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Podman or a standalone <code>runc<\/code> package is still installed and owns files Docker expects to manage. On a fresh host, rerun the removal block from Step 2, then retry the Docker package installation; do not remove a runtime that a current production service still needs without a migration plan.<\/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\">Your SSH session was opened before the account joined the <code>docker<\/code> group, or you ran the group command from a different account. Reconnect, run <code>id -nG<\/code>, and retry. <code>sudo docker info<\/code> is a temporary workaround, while <code>newgrp docker<\/code> reloads group membership in the current shell.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\"><code>curl: (7) Failed to connect to 127.0.0.1 port 8080<\/code><\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The test container stopped, the image is still downloading, or another process already owns port 8080. Check the container state and logs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>docker ps -a --filter name=almalinux-docker-test\ndocker logs almalinux-docker-test\ndocker port almalinux-docker-test<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">If another local service owns the port, repeat the test with <code>--publish 127.0.0.1:18080:80<\/code> and curl <code>http:\/\/127.0.0.1:18080<\/code>. Because the example binds only to loopback, you do not need to open 8080 in firewalld.<\/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 on its Unix socket. Never expose an unauthenticated Docker API on TCP port 2375; remote administration requires a deliberately configured TLS endpoint and narrow firewall rules.<\/li>\n\n\n<li>Treat the <code>docker<\/code> group as root-equivalent access. Use Docker rootless mode when the workload needs a stronger privilege boundary.<\/li>\n\n\n<li>Keep test and internal application ports on <code>127.0.0.1<\/code>. If a web service must be public, publish only ports 80 and 443 and open the matching firewalld services:<\/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>Keep SELinux enabled. When a container bind-mounts a host directory, use a private <code>:Z<\/code> label, for example <code>-v \/srv\/app:\/app:Z<\/code>, instead of weakening file permissions or disabling SELinux.<\/li>\n\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">FAQ<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Is Docker supported on AlmaLinux 10?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">AlmaLinux 10 follows the Enterprise Linux 10 interfaces that Docker&#8217;s RHEL package instructions target. The repository method in this guide uses DNF and Docker&#8217;s <code>el10<\/code> packages, while the exact package versions change as Docker publishes updates.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Should I use Podman instead of Docker on AlmaLinux?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Podman is a strong daemonless option and is commonly available in the AlmaLinux repositories. Choose Docker when your deployment, Compose files, tooling, or team workflow specifically expects the Docker Engine and CLI; do not run both engines casually with overlapping runtime packages.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Where does Docker store images and containers?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">The default data root is <code>\/var\/lib\/docker<\/code>, with containerd data under <code>\/var\/lib\/containerd<\/code>. Keep that space on a filesystem with enough capacity, and use Docker&#8217;s own prune commands only after checking which images, stopped containers, and volumes are disposable.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">How do I publish a container safely?<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Bind internal services to <code>127.0.0.1<\/code> and put 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 firewalld and Docker&#8217;s packet-filtering rules with the application design.<\/p>\n\n\n\n<script type=\"application\/ld+json\">{\"@context\": \"https:\/\/schema.org\", \"@type\": \"FAQPage\", \"mainEntity\": [{\"@type\": \"Question\", \"name\": \"Is Docker supported on AlmaLinux 10?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"AlmaLinux 10 follows the Enterprise Linux 10 interfaces that Docker's RHEL package instructions target. The repository method in this guide uses DNF and Docker's `el10` packages, while the exact package versions change as Docker publishes updates.\"}}, {\"@type\": \"Question\", \"name\": \"Should I use Podman instead of Docker on AlmaLinux?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Podman is a strong daemonless option and is commonly available in the AlmaLinux repositories. Choose Docker when your deployment, Compose files, tooling, or team workflow specifically expects the Docker Engine and CLI; do not run both engines casually with overlapping runtime packages.\"}}, {\"@type\": \"Question\", \"name\": \"Where does Docker store images and containers?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"The default data root is `\/var\/lib\/docker`, with containerd data under `\/var\/lib\/containerd`. Keep that space on a filesystem with enough capacity, and use Docker's own prune commands only after checking which images, stopped containers, and volumes are disposable.\"}}, {\"@type\": \"Question\", \"name\": \"How do I publish a container safely?\", \"acceptedAnswer\": {\"@type\": \"Answer\", \"text\": \"Bind internal services to `127.0.0.1` and put 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 firewalld and Docker's packet-filtering rules with the application design.\"}}]}<\/script>\n\n","protected":false},"excerpt":{"rendered":"Install Docker on AlmaLinux 10 with Docker&#8217;s RPM repository, Compose v2, firewalld-safe testing, non-root access, and fixes for common errors.","protected":false},"author":1,"featured_media":1042,"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":[91,119,118,120],"class_list":["post-1041","post","type-post","status-publish","format-standard","has-post-thumbnail","category-guides","category-vps-hosting","tag-almalinux-10","tag-containers","tag-docker","tag-docker-compose","cs-entry"],"_links":{"self":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1041","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=1041"}],"version-history":[{"count":1,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1041\/revisions"}],"predecessor-version":[{"id":3954,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/posts\/1041\/revisions\/3954"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media\/1042"}],"wp:attachment":[{"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/media?parent=1041"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/categories?post=1041"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/vpslake.com\/blog\/wp-json\/wp\/v2\/tags?post=1041"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}