How to Install Docker on AlmaLinux 9 (Step-by-Step)

Install Docker on AlmaLinux 9 with the official RPM repository, Compose v2, non-root access, a local test, and fixes for common VPS errors quickly.
A polished 3D server rack with glowing container blocks and a subtle green accent on a midnight-blue background.

Install Docker on AlmaLinux 9 with Docker Engine, Compose v2, and a verified container in about ten minutes. The final smoke test stays on localhost, so you can confirm the daemon and networking without exposing an unnecessary port to the internet.

Prerequisites

  • A 64-bit AlmaLinux 9 VPS with at least 2 GB of RAM and 10 GB of free disk space.
  • SSH access as root, or a regular Linux account with working sudo privileges.
  • Outbound HTTPS access to Docker’s RPM repository and Docker Hub.
  • No domain is needed for this installation or its local test. Add one later if you publish an application through a TLS reverse proxy.
  • A VPSLake Linux VPS if you need an AlmaLinux server with root access.

The commands below use sudo for a normal SSH account. If you are already logged in as root, omit sudo; AlmaLinux 9 uses DNF and normally uses firewalld, not Ubuntu’s apt and ufw tools.

Step 1: Confirm the release and update AlmaLinux

Checking the major release before adding a repository prevents DNF from selecting packages for the wrong Enterprise Linux version.

grep -E '^(PRETTY_NAME|VERSION_ID|ID_LIKE)=' /etc/os-release
uname -m
sudo dnf -y upgrade

The release should identify AlmaLinux 9 and the architecture should normally be x86_64:

PRETTY_NAME="AlmaLinux 9.x"
VERSION_ID="9.x"
ID_LIKE="rhel centos fedora"
x86_64

The minor version changes as AlmaLinux publishes updates. Resolve a DNF mirror, DNS, or GPG-signature error before moving on.

Step 2: Clear packages that conflict with Docker

AlmaLinux can have Podman or a standalone runtime installed already, while Docker’s package set supplies its own containerd and related components.

On a new VPS where those tools are not being used, remove the packages that Docker identifies as conflicts:

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 packages are not installed. That is harmless. Do not remove Podman or runc from an active production server until you have checked which services depend on them; removing packages does not by itself delete existing data in /var/lib/docker/.

Step 3: Add Docker’s RHEL-compatible repository

Docker distributes the AlmaLinux-compatible Engine packages through its RHEL repository, which is the maintained source for this installation.

Install the DNF repository plugin, add the signed repository, and refresh package metadata:

sudo dnf install -y dnf-plugins-core ca-certificates
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
sudo dnf makecache

Before installing, confirm that DNF can see an Enterprise Linux 9 build:

dnf list docker-ce --showduplicates | sort -r | head -n 5

The list should contain docker-ce entries from docker-ce-stable with an el9 suffix. Docker’s RHEL installation guide documents the repository, package names, and service setup.

Step 4: Install Docker Engine and Compose

This package set installs the daemon, command-line client, containerd runtime, Buildx builder, and the current docker compose plugin together.

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

Check that systemd started the daemon and that the installed tools can talk to it:

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. Exact patch numbers will change as Docker releases updates:

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

Step 5: Run Docker’s installation check

The official hello-world image checks the full path from the CLI to the daemon, image registry, container creation, and process startup.

sudo docker run --rm hello-world

Near the end of the output, look for:

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

The container exits after printing the message, and --rm removes its stopped container record. The downloaded image remains in Docker’s local cache.

Step 6: Give one trusted account Docker access

The Docker socket is root-owned by default. Membership in the docker group avoids repeated sudo, but it also gives that account root-equivalent control over the host.

Run this block while logged in as the normal account that should manage Docker:

sudo groupadd --force docker
sudo usermod -aG docker "$USER"

Sign out of SSH completely and reconnect so the new group membership is loaded. Then verify that the account can reach the daemon without sudo:

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

The group list should include docker, and the second command should print the Engine version. newgrp docker can activate the membership in the current shell, but a fresh SSH login avoids carrying stale group credentials.

Step 7: Test container networking on localhost

A loopback-only web container proves that Docker can publish a port while keeping the test inaccessible from outside the VPS.

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

You should see a running container and an HTTP success response:

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

Because the binding uses 127.0.0.1, do not open port 18080 in firewalld. Remove the disposable test after checking it:

docker stop almalinux9-docker-check
docker rm almalinux9-docker-check

Verify Docker on AlmaLinux 9

Run this compact check to confirm boot startup, 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, Docker client/server versions, a Compose v2 version, and commonly the overlay2 storage driver:

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

Docker is ready for a Compose project or application image. For the interaction between published ports, Docker-created rules, and firewalld, read Docker’s packet-filtering and firewalls documentation.

Troubleshooting

No match for argument: docker-ce

DNF has not loaded the Docker repository, or the repository metadata is stale. Confirm the enabled repo and refresh the cache:

sudo dnf repolist --enabled | grep -i docker
sudo dnf makecache
dnf list docker-ce --showduplicates

You should see Docker’s stable repository and an el9 package candidate. If the repo is absent, repeat the dnf config-manager --add-repo command in Step 3.

package podman ... conflicts with ...

Podman or a separate runc package is still installed and owns a file expected by Docker’s package set. On a new VPS, rerun Step 2; on a working host, identify the dependent service and plan the runtime change before removing anything.

permission denied while trying to connect to the Docker daemon socket

The SSH session was started before the account joined the docker group, or the group command was run as a different user. Reconnect and check id -nG; use sudo docker info temporarily or run newgrp docker for the current shell.

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

The test container may have exited, the image may still be downloading, or another process may already own port 18080. Inspect the container and logs:

docker ps -a --filter name=almalinux9-docker-check
docker logs almalinux9-docker-check
docker port almalinux9-docker-check

If the port is busy, rerun the container with another loopback port such as 127.0.0.1:28080:80 and curl http://127.0.0.1:28080. No public firewall rule is needed for either binding.

Hardening

  • Keep Docker administration on its Unix socket. Never expose an unauthenticated Docker API on TCP port 2375; remote access needs deliberate TLS configuration and narrow firewall rules.
  • Treat the docker group as administrative access. Use Docker rootless mode when the workload needs a stronger privilege boundary.
  • Publish internal services on 127.0.0.1 and place a TLS reverse proxy in front of them. For a public web service, open only the matching firewalld services:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
  • Keep SELinux enabled. For a bind mount, use Docker’s :Z label for a private container mount, such as -v /srv/app:/app:Z, instead of disabling the policy.

FAQ

Does Docker support AlmaLinux 9?

Docker’s documented RHEL procedure lists RHEL 9, and AlmaLinux 9 provides the compatible Enterprise Linux package interfaces used by that repository. The repository uses el9 packages; their patch versions change as Docker publishes updates.

Should I remove Podman before installing Docker?

On a fresh VPS, removing unused Podman and standalone runc packages avoids package conflicts. If Podman is serving workloads, keep it until you have planned a migration because Docker and Podman are separate container engines.

Where does Docker keep its data on AlmaLinux 9?

Docker normally stores images, writable layers, and container metadata under /var/lib/docker, while containerd keeps runtime data under /var/lib/containerd. Monitor the filesystem that holds these paths before pulling large images.

Do I need to open a port for Docker?

No port is needed for the installation or the localhost smoke test. When you publish a real application, expose only its required port and review Docker’s packet-filtering behavior alongside firewalld; a published port is a network decision, not just a container setting.

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

Best RDP for Kenya in 2026: Price, Payment and Speed

Next Article

Cheap VPS for WordPress in 2026: RAM and Setup

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 ✨