This guide shows how to install docker compose on almalinux 8 with Docker Engine and the maintained Docker Compose CLI plugin. In about ten minutes, you will launch a small web-and-cache stack and verify it without exposing the test service to the public internet.
Prerequisites
- A 64-bit AlmaLinux 8 VPS with at least 2 GB of RAM and 10 GB of free disk space.
- SSH access as root, or a normal Linux account with working sudo privileges.
- Outbound HTTPS access to Docker’s RPM repository and Docker Hub.
- A VPSLake Linux VPS if you need an AlmaLinux server with root access.
- No domain is needed for this private test. Use a domain later when you put a public application behind a TLS reverse proxy.
The commands use sudo for a normal SSH account. If you are logged in as root, remove sudo. AlmaLinux 8 uses DNF and firewalld, so the package and firewall commands below are not interchangeable with Ubuntu’s apt and ufw commands.
Step 1: Confirm AlmaLinux 8 and update the server
Confirming the release first prevents you from adding a repository for the wrong Enterprise Linux version.
~~~bash grep -E ‘^(PRETTY_NAME|VERSION_ID|ID_LIKE)=’ /etc/os-release uname -m sudo dnf -y upgrade ~~~
The result should identify AlmaLinux 8 and a supported 64-bit architecture:
~~~text PRETTY_NAME=”AlmaLinux 8.x (Sapphire Caracal)” VERSION_ID=”8.x” ID_LIKE=”rhel centos fedora” x86_64 ~~~
The minor version and package update count will vary. If DNF reports a DNS, mirror, or GPG-signature error, resolve that before installing Docker.
Step 2: Remove packages that conflict with Docker
Docker’s official packages include the Docker runtime components they need, so remove alternative packages from a fresh VPS before adding Docker’s repository.
~~~bash sudo dnf remove -y docker \ docker-client \ docker-client-latest \ docker-common \ docker-latest \ docker-latest-logrotate \ docker-logrotate \ docker-engine \ podman \ runc ~~~
It is normal for DNF to say that some packages are not installed. On a server already running Podman or containers, inspect those workloads before removing anything; the command is intended for a new or disposable host. Removing the packages does not automatically erase existing Docker data under /var/lib/docker.
Step 3: Add Docker’s AlmaLinux-compatible RPM repository
The RHEL-compatible Docker repository supplies the Engine, CLI, Buildx, and Compose plugin packages through DNF, which also makes later updates straightforward.
~~~bash 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 ~~~
Check that DNF can see an AlmaLinux 8-compatible Engine package:
~~~bash dnf list –showduplicates docker-ce | tail -n 5 ~~~
You should see docker-ce entries from Docker’s stable repository with an el8 package suffix. Docker documents this repository method in its RHEL installation guide; AlmaLinux 8 uses the compatible Enterprise Linux package interfaces.
Step 4: Install Docker Engine and the Compose plugin
The current Compose integration is delivered as a Docker CLI plugin, so install it with Engine instead of downloading an old standalone docker-compose executable.
~~~bash sudo dnf install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin sudo systemctl enable –now docker sudo systemctl is-active docker sudo docker version –format ‘Client {{.Client.Version}} / Server {{.Server.Version}}’ sudo docker compose version ~~~
The service should be active, and the last two checks should print version strings. Exact plugin and Engine versions change as Docker publishes updates:
~~~text active Client X.Y.Z / Server X.Y.Z Docker Compose version vX.Y.Z ~~~
Run Docker’s small end-to-end smoke test before creating a project:
~~~bash sudo docker run –rm hello-world ~~~
Near the end of the output, look for:
~~~text Hello from Docker! Your installation appears to be working correctly. ~~~
Step 5: Allow your SSH account to run Compose
The Docker socket is root-owned by default. Adding one trusted account to the docker group avoids sudo, but group membership is effectively administrative access to the server.
Replace YOUR_LINUX_USER with the account that will manage this project:
~~~bash sudo usermod -aG docker YOUR_LINUX_USER ~~~
Log out of SSH and reconnect so the group change is loaded. Then check access without sudo:
~~~bash id -nG docker info –format ‘Server {{.ServerVersion}}’ ~~~
The group list should include docker, and docker info should print the Engine version. Use newgrp docker for a one-shell test, or continue using sudo docker compose until you open a new session.
Step 6: Create a two-service Compose project
This test uses Nginx and Redis to demonstrate a private Compose network, a health check, a named volume, and a loopback-only web binding.
Create the project directory and a page for Nginx:
~~~bash mkdir -p ~/compose-alma8/site cd ~/compose-alma8 printf ‘%s\n’ ‘Docker Compose on AlmaLinux 8 is running.’ > site/index.html ~~~
Store a long, unique Redis password in a file readable only by your account:
~~~bash tee .env >/dev/null <<‘EOF’ REDIS_PASSWORD=YOUR_STRONG_REDIS_PASSWORD EOF chmod 600 .env ~~~
Create compose.yaml with this configuration:
~~~bash tee compose.yaml >/dev/null <<‘EOF’ services: web: image: nginx:alpine ports:
- “127.0.0.1:8088:80” volumes:
- ./site:/usr/share/nginx/html:ro,Z depends_on: cache: condition: service_healthy
cache: image: redis:7-alpine env_file:
- .env command: [“sh”, “-c”, “exec redis-server –requirepass \”$$REDIS_PASSWORD\””] volumes:
- redis_data:/data healthcheck: test: [“CMD-SHELL”, “redis-cli -a \”$$REDIS_PASSWORD\” ping | grep PONG”] interval: 5s timeout: 3s retries: 12
volumes: redis_data: EOF ~~~
The :Z suffix gives SELinux an appropriate private label for the bind mount. The Nginx port listens only on loopback, while Redis has no published port and is reachable only by the other service on Compose’s network. The named volume keeps Redis data when its container is replaced. See Docker’s Compose file reference for the service, volume, and health-check fields used here.
Step 7: Validate and start the stack
Render the file before pulling images so YAML indentation and the password file are checked first.
~~~bash docker compose config –quiet docker compose pull docker compose up -d docker compose ps ~~~
The first command should be silent and return status 0. After the images download, both services should be up and Redis should be healthy:
~~~text NAME SERVICE STATUS PORTS compose-alma8-web-1 web Up 127.0.0.1:8088->80/tcp compose-alma8-cache-1 cache Up (healthy) ~~~
The exact container names can differ if you run the project from another directory or set a project name.
Verify Docker Compose on AlmaLinux 8
These checks confirm the Compose plugin can render the configuration, serve the mounted page, authenticate to Redis, and report recent service logs.
~~~bash curl –fail –silent –show-error http://127.0.0.1:8088 docker compose exec cache sh -c ‘redis-cli -a “$REDIS_PASSWORD” ping’ docker compose logs –tail 20 web cache ~~~
The first two checks should return:
~~~text Docker Compose on AlmaLinux 8 is running. PONG ~~~
Because Nginx is bound to 127.0.0.1, it is not reachable directly from your workstation. For private testing, create an SSH tunnel from your workstation with ssh -L 8088:127.0.0.1:8088 USER@SERVER_IP, then open http://127.0.0.1:8088 locally.
Troubleshooting
docker: ‘compose’ is not a docker command
The Compose CLI plugin is missing, or the Docker package came from a different repository. Refresh DNF metadata and install the plugin from Docker’s repository:
~~~bash sudo dnf makecache sudo dnf install -y docker-compose-plugin docker compose version ~~~
Use a space in docker compose. The hyphenated docker-compose command is the legacy standalone tool and is not required for this setup.
No match for argument: docker-ce
DNF is not reading Docker’s repository, or its metadata update failed. Check the enabled repositories and rebuild the cache:
~~~bash sudo dnf repolist –enabled | grep -i docker sudo dnf makecache dnf list –showduplicates docker-ce ~~~
The list should include Docker’s stable repository and an el8 build. If the repository is absent, repeat the dnf config-manager –add-repo command from Step 3.
permission denied while trying to connect to the Docker daemon socket
Your current SSH session has not loaded the new group membership, or usermod was run for a different account. Reconnect, confirm id -nG includes docker, and retry. sudo docker compose … is a temporary workaround.
The cache is unhealthy or reports WRONGPASS
The password in .env may be missing, changed after the container was created, or contain an unintended space. Inspect the rendered configuration and cache logs:
~~~bash docker compose config docker compose logs cache ~~~
For this disposable test, recreate the service after correcting .env:
~~~bash docker compose up -d –force-recreate cache ~~~
Do not add -v to docker compose down on a real application; that option deletes named volumes and their data.
Hardening
- Keep .env at mode 600, exclude it from source control, and use a managed secret store for production credentials.
- Do not publish Redis or Docker’s unauthenticated TCP API. Keep internal services on the Compose network or on loopback.
- If you later expose a web application through a TLS reverse proxy, open only HTTP and HTTPS in firewalld:
~~~bash sudo firewall-cmd –permanent –add-service=http sudo firewall-cmd –permanent –add-service=https sudo firewall-cmd –reload ~~~
- Keep SELinux enabled. Use :Z for a private bind mount or :z when the same host directory is intentionally shared by multiple containers.
FAQ
Is Docker Compose installed with Docker Engine on AlmaLinux 8?
Not automatically on every installation. The official Docker RPM repository provides docker-compose-plugin separately, and the installation is complete only after docker compose version returns a plugin version.
What is the difference between docker compose and docker-compose?
docker compose is the current Docker Compose CLI plugin and uses the Compose Specification. docker-compose is the older standalone command kept mainly for compatibility, so new projects should use the space-separated command.
Does docker compose down delete the Redis data?
Normal docker compose down removes the project containers and network but keeps the redis_data named volume. Adding -v removes that volume, so use it only when intentionally resetting a disposable test.
How do I make the sample page public?
Do not simply change the binding to an unrestricted high port. Put Nginx or another application behind a TLS reverse proxy, publish only the required web ports, permit 80 and 443 in firewalld, and keep Redis private.