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.
Prerequisites
- A 64-bit AlmaLinux 8 VPS with at least 2 GB of RAM and 10 GB of available disk space.
- SSH access as
root, or a regular account with workingsudoprivileges. - Outbound HTTPS access to Docker’s RPM repository and Docker Hub.
- 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.
- A VPSLake Linux VPS if you need an AlmaLinux server with root access.
These commands use sudo for a normal SSH account. If you are already logged in as root, omit sudo. AlmaLinux 8 uses DNF and normally uses firewalld, so do not replace the commands with Ubuntu’s apt or ufw equivalents.
Step 1: Confirm AlmaLinux 8 and update the host
Checking the release before changing repositories helps prevent installing Enterprise Linux packages for the wrong major version.
grep -E '^(PRETTY_NAME|VERSION_ID|ID_LIKE)=' /etc/os-release
uname -m
sudo dnf -y upgrade
The release should show AlmaLinux 8 and the architecture should normally be x86_64:
PRETTY_NAME="AlmaLinux 8.x (Sapphire Caracal)"
VERSION_ID="8.x"
ID_LIKE="rhel centos fedora"
x86_64
The exact minor release changes as AlmaLinux publishes updates. If DNF reports a mirror, DNS, or GPG-signature problem, fix that before continuing.
Step 2: Remove packages that can conflict with Docker Engine
AlmaLinux may include Podman or a separate runtime, while Docker’s packages bring their own compatible containerd and CLI components.
On a new VPS, remove the possible conflicting packages:
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 normal. Removing packages does not automatically delete Docker data in /var/lib/docker/, but do not remove Podman or runc from an existing production host until you know which workloads use them.
Step 3: Add Docker’s signed RPM repository
The official repository gives DNF a maintained package source for Docker Engine, Buildx, Compose, and the container runtime.
Install the repository-management plugin and add Docker’s RHEL-compatible repository:
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
List available Engine builds to confirm that DNF can see the repository:
dnf list --showduplicates docker-ce | tail -n 5
You should see docker-ce entries from docker-ce-stable, normally with an el8 suffix. Docker’s RHEL installation documentation documents this repository method; AlmaLinux 8 uses the compatible Enterprise Linux package interfaces.
Step 4: Install Docker Engine and Compose v2
This package set installs the daemon, CLI, containerd, Buildx, and the modern docker compose subcommand in one transaction.
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 Docker and that the client can reach the daemon:
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. The patch numbers will change over time:
active
enabled
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y
Step 5: Run a disposable container test
The hello-world image checks the complete path from the CLI to the daemon, image registry, container creation, and process startup.
sudo docker run --rm hello-world
Near the end, Docker should print:
Hello from Docker!
Your installation appears to be working correctly.
The container exits on its own, and --rm removes its stopped container record. The downloaded image remains cached until you remove it.
Step 6: Let a regular account use Docker
The Docker socket is root-owned by default. Adding one trusted account to the docker group avoids repeated sudo, but group membership grants root-equivalent control over the host.
Run this command as the account that should manage containers, replacing the placeholder with its Linux username:
sudo usermod -aG docker YOUR_LINUX_USER
Log out of SSH completely and reconnect so the new group membership is loaded. Then verify access without sudo:
id -nG
docker info --format 'Server {{.ServerVersion}}'
The group list should include docker, and the second command should print the Docker Engine version. For a one-shell test, newgrp docker reloads group membership, but a fresh login is clearer.
Step 7: Test container networking without opening a public port
Publishing the test port on 127.0.0.1 checks Docker networking while keeping it unreachable from the internet.
docker run -d --name almalinux8-docker-test --publish 127.0.0.1:8080:80 nginx:alpine
docker ps --filter name=almalinux8-docker-test --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
curl --head http://127.0.0.1:8080
The container should be running and curl should receive an HTTP success response:
NAMES STATUS PORTS
almalinux8-docker-test Up ... 127.0.0.1:8080->80/tcp
HTTP/1.1 200 OK
Remove this disposable container when you are done:
docker stop almalinux8-docker-test
docker rm almalinux8-docker-test
Verify Docker on AlmaLinux 8
Run one final check for service 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}}'
Expected results include active, enabled, Docker client/server versions, Compose v2, 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 an application image. For how published ports interact with Linux packet filtering, see Docker’s packet-filtering and firewalls guide.
Troubleshooting
No match for argument: docker-ce
DNF has not loaded Docker’s repository, or the repository file was added for a different Enterprise Linux release. Check the enabled repositories and rebuild metadata:
sudo dnf repolist --enabled | grep -i docker
sudo dnf makecache
dnf list --showduplicates docker-ce
The list should contain Docker’s stable repository and an AlmaLinux 8-compatible build. If the repository is missing, repeat the dnf config-manager --add-repo command from Step 3.
package podman ... conflicts with ...
Podman or a standalone runc package is still installed and owns files that Docker’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.
permission denied while trying to connect to the Docker daemon socket
The SSH session was opened before the account joined the docker group, or the group command was run for a different user. Reconnect, check id -nG, and retry. sudo docker info is a temporary workaround, while newgrp docker reloads the group in the current shell.
permission denied when a container reads a bind mount
SELinux is correctly preventing a container from reading a host directory without an appropriate label. Keep SELinux enabled and add the private :Z relabel option to a bind mount, for example:
docker run --rm -v /srv/app:/app:Z YOUR_IMAGE
Replace YOUR_IMAGE with the image you intend to run. Use :z 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.
Hardening
- Never expose Docker’s unauthenticated remote API on TCP port 2375. Keep administration on the Unix socket, or configure a deliberate TLS endpoint with narrow firewall rules.
- Treat membership in the
dockergroup as administrative access. Use rootless Docker when the workload needs a stronger privilege boundary. - Keep internal services on
127.0.0.1. If an application must be public, publish only the required ports and open matching firewalld services, such as HTTP and HTTPS:
sudo firewall-cmd --permanent --add-service=http
sudo firewall-cmd --permanent --add-service=https
sudo firewall-cmd --reload
- Do not disable SELinux to make a container work. Use the correct
:Zor:zmount label and review the application’s file permissions instead.
FAQ
Does Docker support AlmaLinux 8?
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.
Is Docker or Podman better on AlmaLinux 8?
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.
Where are Docker images and containers stored?
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.
How do I expose a container safely?
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.