Docker Examples on AlmaLinux 10: Install, Run, Build and Compose

Docker is still the quickest way to package and run applications on a Linux server, and AlmaLinux makes a solid, long-lived host for it. This guide installs Docker Engine from Docker’s official repository on AlmaLinux 10, then works through practical examples you will actually use: running containers, publishing ports, persistent volumes, building your own image from a Dockerfile, user-defined networks and a multi-container stack with Docker Compose.

Versions at the time of writing: AlmaLinux 10.2 and Docker Engine 29.9 with the Compose v2 and Buildx plugins. Docker publishes packages for RHEL 8, 9 and 10, so the same steps work on AlmaLinux 9, Rocky Linux, Oracle Linux, RHEL and CentOS Stream. For the bigger picture on where containers are heading, see Docker in 2026: The Future of Containers, Linux, and Self-Hosted Infrastructure.

Prerequisites

  • An AlmaLinux 10 server (physical, VM or cloud instance) with at least 2 GB RAM and 20 GB free disk
  • A user with sudo rights
  • Internet access to download.docker.com and Docker Hub
  • Ports you plan to publish (8080 and 8081 in the examples) reachable from your workstation if you want to test from a browser

Step 1: Remove Podman and other conflicting packages

AlmaLinux ships Podman and Buildah, and some images or groups pull in runc and podman-docker. These conflict with Docker’s own packages, so remove them first. It is fine if dnf reports that none are installed.

sudo dnf remove docker docker-client docker-client-latest docker-common \
  docker-latest docker-latest-logrotate docker-logrotate docker-engine \
  podman podman-docker buildah runc

Existing Docker data in /var/lib/docker is not deleted by this step.

Step 2: Add the official Docker CE repository

sudo dnf -y install dnf-plugins-core
sudo dnf config-manager --add-repo https://download.docker.com/linux/rhel/docker-ce.repo
sudo dnf repolist | grep docker

Use the rhel repository on every Enterprise Linux rebuild; the $releasever variable in the repo file resolves to your major version, so AlmaLinux 10 gets the el10 packages.

Step 3: Install Docker Engine, Buildx and Compose

sudo dnf install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin

When dnf asks you to import the GPG key, confirm the fingerprint is 060A 61C5 1B55 8A7F 742B 77AA C52F EB6B 621E 9F35 before accepting. Then enable and start the services:

sudo systemctl enable --now docker containerd
systemctl status docker --no-pager

Step 4: Run Docker as a regular user (optional)

The installer creates a docker group but adds nobody to it. Membership in that group is effectively root on the host, so only add trusted administrators.

sudo usermod -aG docker $USER
newgrp docker      # or log out and back in

Step 5: Verify the installation

docker version
docker info | grep -E 'Server Version|Storage Driver|Cgroup'
docker compose version
docker buildx version
docker run --rm hello-world

You should see client and server versions, the overlayfs or overlay2 storage driver, cgroup version 2, and the “Hello from Docker!” message.

Example 1: Run a web server and publish a port

docker run -d --name web -p 8080:80 --restart unless-stopped nginx:stable
docker ps
curl -I http://localhost:8080

-d runs the container in the background, -p 8080:80 maps host port 8080 to port 80 inside the container, and --restart unless-stopped brings it back after a reboot. Everyday management commands:

docker logs -f web          # follow logs (Ctrl+C to stop)
docker exec -it web bash    # shell inside the container
docker stats --no-stream    # CPU / memory per container
docker stop web && docker rm web

Example 2: Persistent data with volumes and bind mounts

Containers are disposable; data belongs in a volume (managed by Docker) or a bind mount (a host directory).

# Named volume
docker volume create webdata
docker run -d --name web -p 8080:80 -v webdata:/usr/share/nginx/html nginx:stable
docker volume inspect webdata

# Bind mount a host directory read-only
mkdir -p ~/site && echo '<h1>Hello from AlmaLinux</h1>' > ~/site/index.html
docker rm -f web
docker run -d --name web -p 8080:80 -v ~/site:/usr/share/nginx/html:ro nginx:stable
curl http://localhost:8080

If you enable SELinux separation for containers (see Troubleshooting), add :Z to bind mounts, for example -v ~/site:/usr/share/nginx/html:ro,Z, so Docker relabels the directory for container access.

Example 3: Build your own image from a Dockerfile

This builds a small web server image on the official AlmaLinux 10 minimal base image.

mkdir -p ~/almaweb && cd ~/almaweb
echo '<h1>Built on AlmaLinux 10</h1>' > index.html

Create ~/almaweb/Dockerfile:

FROM almalinux:10-minimal
RUN microdnf -y install httpd && microdnf clean all \
 && sed -i 's/^Listen 80$/Listen 8080/' /etc/httpd/conf/httpd.conf
COPY index.html /var/www/html/index.html
EXPOSE 8080
CMD ["httpd", "-DFOREGROUND"]

Build, run and test it:

docker build -t almaweb:1.0 .
docker images almaweb
docker run -d --name almaweb -p 8081:8080 almaweb:1.0
curl http://localhost:8081

Tag and push the image to a registry when you want to reuse it on other hosts, for example docker tag almaweb:1.0 registry.example.com/almaweb:1.0 followed by docker push registry.example.com/almaweb:1.0 after docker login registry.example.com.

Example 4: User-defined networks

Containers on the same user-defined bridge network can reach each other by container name through Docker’s built-in DNS. The default bridge network does not offer this.

docker network create appnet
docker run -d --name cache --network appnet redis:alpine
docker run --rm --network appnet redis:alpine redis-cli -h cache ping
docker network inspect appnet

The last run command should print PONG. Clean up with docker rm -f cache and docker network rm appnet.

Example 5: A multi-container stack with Docker Compose

Compose describes a whole application in one YAML file. This stack runs PostgreSQL 18 with a persistent volume and Adminer as a web-based database client.

mkdir -p ~/pgstack && cd ~/pgstack
printf 'POSTGRES_PASSWORD=%s\n' "$(openssl rand -base64 24)" > .env
chmod 600 .env

Create ~/pgstack/compose.yaml:

services:
  db:
    image: postgres:18
    restart: unless-stopped
    environment:
      POSTGRES_USER: app
      POSTGRES_DB: appdb
      POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
    volumes:
      - pgdata:/var/lib/postgresql
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U app -d appdb"]
      interval: 10s
      timeout: 5s
      retries: 5

  adminer:
    image: adminer:latest
    restart: unless-stopped
    ports:
      - "127.0.0.1:8082:8080"
    depends_on:
      db:
        condition: service_healthy

volumes:
  pgdata:

PostgreSQL 18 images store data in a version-specific directory under /var/lib/postgresql, so the volume is mounted there rather than at the older /var/lib/postgresql/data path. Start and check the stack:

docker compose up -d
docker compose ps
docker compose logs db --tail 20
docker compose exec db psql -U app -d appdb -c 'select version();'

Adminer is bound to 127.0.0.1 only; reach it through an SSH tunnel with ssh -L 8082:localhost:8082 user@server and browse to http://localhost:8082 (server db, user app). Stop the stack with docker compose down, or docker compose down -v to also delete the database volume.

Housekeeping and updates

docker system df              # disk used by images, containers, volumes
docker image prune -a          # remove unused images
docker system prune            # remove stopped containers, unused networks and dangling images
sudo dnf upgrade docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
docker compose pull && docker compose up -d   # update a Compose stack

To keep container logs from filling the disk, set log rotation in /etc/docker/daemon.json and restart Docker (it applies to newly created containers):

{
  "log-driver": "json-file",
  "log-opts": { "max-size": "10m", "max-file": "3" }
}
sudo systemctl restart docker

Firewall notes

Docker writes its own packet-filter rules for published ports, so a port published with -p is reachable from the network even if you never opened it in firewalld. To keep a service private, publish it on the loopback address (-p 127.0.0.1:8080:80) as the Compose example does for Adminer, or put a reverse proxy in front. Opening a port in firewalld is only needed for services running directly on the host:

sudo firewall-cmd --get-active-zones
sudo firewall-cmd --permanent --add-port=8080/tcp
sudo firewall-cmd --reload

Troubleshooting

  • Package conflicts with runc or podman during install. Re-run Step 1, then install again. dnf repoquery --installed --whatprovides runc shows what still provides it.
  • permission denied while trying to connect to the Docker daemon socket. Your session is not in the docker group yet. Run id; if docker is missing, log out and back in after Step 4, or use sudo.
  • Cannot connect to the Docker daemon. Check sudo systemctl status docker and sudo journalctl -u docker -b --no-pager | tail -50. A JSON syntax error in /etc/docker/daemon.json is the most common cause; validate it with python3 -m json.tool /etc/docker/daemon.json.
  • bind: address already in use. Another process holds the host port. Find it with sudo ss -ltnp | grep :8080 and choose a different host port.
  • Permission denied on bind-mounted files. Docker CE does not enable SELinux container separation by default. If you set "selinux-enabled": true in daemon.json, add :Z (private) or :z (shared) to bind mounts and check denials with sudo ausearch -m avc -ts recent. Also check that the container’s user ID can read the host files.
  • Containers cannot reach the internet. Confirm docker run --rm alpine ping -c2 1.1.1.1 works, then test DNS with docker run --rm alpine nslookup almalinux.org. Restarting Docker after firewalld reloads or rule changes recreates its rules: sudo systemctl restart docker.
  • Pull rate limits from Docker Hub. Run docker login with a Docker Hub account, or mirror images in a private registry.

Next steps

With Docker running, the natural next step is orchestration across several hosts; read Kubernetes in 2026 for where container orchestration is heading. If you need shared persistent storage for containers, the Ceph on AlmaLinux 10 with cephadm guide builds the storage layer. The official Docker Engine install guide for RHEL and Compose documentation cover every option used here.

Leave a Reply

Your email address will not be published. Required fields are marked *