How to Install Docker Compose on Rocky Linux 8

Install Docker Compose on Rocky Linux 8 with Docker Engine, the Compose plugin, a private PostgreSQL test stack, and common fixes in minutes on a fresh VPS.
A polished 3D server rack with glowing container blocks and a subtle deep-red accent on a midnight-blue background.

Install Docker Compose on Rocky Linux 8 with Docker Engine and Docker’s maintained Compose CLI plugin. In about ten minutes, you will launch a private PostgreSQL and Adminer stack, verify both services, and keep the database UI off the public network.

Prerequisites

  • A 64-bit Rocky Linux 8 VPS with at least 2 GB of RAM and 10 GB of free disk space.
  • SSH access as root, or a regular account with working sudo privileges.
  • Outbound HTTPS access to Docker’s RPM repository and container registry.
  • A VPSLake Linux VPS if you need a Rocky Linux server with root access.
  • No domain is needed for this private test. Use a domain and a TLS reverse proxy before exposing a real web application.

Rocky Linux 8 uses DNF and firewalld. The commands below use sudo for a normal SSH account; if you are root, remove sudo and keep the same command order.

Step 1: Confirm Rocky Linux 8 and update the host

Checking the release and architecture first prevents an Enterprise Linux repository mismatch.

~~~bash . /etc/os-release printf ‘%s\n’ “$PRETTY_NAME” printf ‘Major release: %s\n’ “$(rpm -E %rhel)” uname -m sudo dnf upgrade -y ~~~

The first lines should identify Rocky Linux 8 and a 64-bit machine:

~~~text Rocky Linux 8.x (Green Obsidian) Major release: 8 x86_64 ~~~

The minor release and number of updated packages will vary. Resolve any DNS, mirror, or GPG-signature error before installing Docker.

Step 2: Remove conflicting container packages on a fresh host

Docker’s official RPM packages can conflict with an existing Docker or Podman setup, so clear those packages only if this VPS does not already run containers you need.

~~~bash 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 names are not installed. Do not run this step on a production host with active Podman workloads until you have inspected and migrated them. Removing packages does not delete Docker data stored in /var/lib/docker.

Step 3: Add Docker’s Rocky-compatible RPM repository

The RHEL-compatible Docker repository provides packages that DNF can update together, including the Compose plugin.

~~~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 sudo dnf repolist –enabled | grep -i docker ~~~

The final command should list Docker’s stable repository. Docker’s RHEL installation guide supports RHEL 8; Rocky Linux 8 uses the same Enterprise Linux package interfaces.

Step 4: Install Docker Engine and the Compose plugin

Compose v2 is a Docker CLI plugin, so install it from the RPM repository instead of downloading the obsolete standalone docker-compose binary.

~~~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 ~~~

You should see an active service and version strings. Exact versions change as Docker publishes updates:

~~~text active Client 29.x / Server 29.x Docker Compose version vX.Y.Z ~~~

Run Docker’s disposable smoke test to confirm that the daemon can pull and start an image:

~~~bash sudo docker run –rm hello-world ~~~

The final part of the response should contain:

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

Step 5: Let your SSH account run Docker

The Docker socket is root-owned by default. Adding one trusted administrator to the docker group avoids sudo, but that group grants root-level control over the host.

Replace YOUR_LINUX_USER with the account you use for SSH:

~~~bash sudo usermod -aG docker YOUR_LINUX_USER ~~~

Log out of SSH completely and reconnect so the new group membership 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 an Engine version. If you prefer not to grant permanent group access, retain sudo on every Docker command instead.

Step 6: Create a private PostgreSQL Compose project

This example uses Adminer as a database web client and PostgreSQL as its backend. The database has no published port, and Adminer listens only on the VPS loopback interface.

Create a project directory and a protected environment file:

~~~bash mkdir -p ~/compose-rocky8 cd ~/compose-rocky8 tee .env >/dev/null <<‘EOF’ POSTGRES_DB=appdb POSTGRES_USER=appuser POSTGRES_PASSWORD=YOUR_STRONG_DATABASE_PASSWORD EOF chmod 600 .env ~~~

Replace YOUR_STRONG_DATABASE_PASSWORD with a long, unique password before starting the services. Do not commit .env to a public repository.

Create compose.yaml:

~~~yaml services: db: image: postgres:16-alpine restart: unless-stopped environment: POSTGRES_DB: ${POSTGRES_DB} POSTGRES_USER: ${POSTGRES_USER} POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} volumes:

  • pgdata:/var/lib/postgresql/data healthcheck: test: [“CMD-SHELL”, “pg_isready -U \”$$POSTGRES_USER\” -d \”$$POSTGRES_DB\””] interval: 5s timeout: 5s retries: 12

adminer: image: adminer:4 restart: unless-stopped depends_on: db: condition: service_healthy environment: ADMINER_DEFAULT_SERVER: db ports:

  • “127.0.0.1:8086:8080”

volumes: pgdata: ~~~

Compose substitutes the values from .env on the host, while $$ passes the variable names into the database health check. The depends_on condition keeps Adminer from starting before PostgreSQL is accepting connections. The pgdata named volume preserves database files when the container is recreated. See Docker’s Compose file reference for the service, health-check, and volume fields.

Step 7: Render and start the stack

Rendering the file first catches YAML and variable-substitution errors before containers are created.

~~~bash docker compose config –quiet docker compose pull docker compose up -d docker compose ps ~~~

config --quiet should be silent and return status 0. After the images download, both services should be running and PostgreSQL should be healthy:

~~~text NAME SERVICE STATUS PORTS compose-rocky8-db-1 db Up (healthy) compose-rocky8-adminer-1 adminer Up 127.0.0.1:8086->8080/tcp ~~~

Container names may differ if you choose another directory or set a Compose project name.

Verify Docker Compose on Rocky Linux 8

Use an HTTP request and a PostgreSQL readiness check to prove that Compose controls the stack and the services can communicate:

~~~bash curl –fail –silent –show-error http://127.0.0.1:8086/ | grep -o ‘<title>[^<]*</title>’ docker compose exec -T db pg_isready -U appuser -d appdb docker compose logs –tail=20 db adminer ~~~

The first two checks should produce output similar to:

~~~text <title>Login – Adminer</title> /var/run/postgresql:5432 – accepting connections ~~~

To open the Adminer page from your own workstation without publishing port 8086, create an SSH tunnel there:

~~~bash ssh -L 8086:127.0.0.1:8086 YOUR_LINUX_USER@SERVER_IP ~~~

Then visit http://127.0.0.1:8086 locally. Use db as the server, appuser as the username, appdb as the database, and the password saved in .env.

Troubleshooting

No match for argument: docker-ce

DNF is not reading the Docker repository, or its metadata refresh failed. Check the repository and refresh the cache:

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

The package list should include an el8 build. If the first command returns nothing, repeat the repository command in Step 3 and check that download.docker.com resolves from the VPS.

docker: 'compose' is not a docker command

The CLI plugin is missing or the Docker packages came from another repository. Install the plugin after refreshing DNF metadata:

~~~bash sudo dnf makecache sudo dnf install -y docker-compose-plugin docker compose version ~~~

Use docker compose with a space. The hyphenated command is the legacy standalone form and is not needed for this installation.

permission denied while trying to connect to the Docker daemon socket

Your current SSH session still has its old group list, or usermod targeted a different account. Reconnect, confirm that id -nG includes docker, and retry. sudo docker compose ... is a temporary workaround.

Adminer or a bind mount returns a permission error

A browser on another machine cannot reach Adminer because the sample intentionally binds it to 127.0.0.1; use the SSH tunnel from the Verify section. For a host-directory bind mount on Rocky Linux, add :Z to that mount so SELinux assigns a private container label, then recreate the service:

~~~bash docker compose up -d –force-recreate SERVICE_NAME docker compose logs –tail=30 SERVICE_NAME ~~~

Keep SELinux enabled. Correct the mount label or ownership instead of disabling the policy.

Hardening

  • Keep database ports and the Docker API unpublished. If you later put a real web service behind a TLS reverse proxy, allow only its public services in firewalld:

~~~bash sudo firewall-cmd –permanent –add-service=http sudo firewall-cmd –permanent –add-service=https sudo firewall-cmd –reload ~~~

  • Limit membership in the docker group to trusted administrators because Docker socket access is effectively root-level access.
  • Keep .env out of source control, review image tags before production use, and back up pgdata before running docker compose down -v, which deletes named volumes.

FAQ

Is docker compose different from docker-compose?

Yes. docker compose is Compose v2 delivered as a Docker CLI plugin and is the current command used by this guide. docker-compose is the older standalone form kept for compatibility.

Do I need to disable SELinux for Docker Compose on Rocky Linux 8?

No. Keep SELinux enforcing. When you bind-mount a host directory, use the :Z option for a private label and recreate the affected container.

Why is the Adminer port not open in firewalld?

The sample binds port 8086 to 127.0.0.1, so it is reachable only from the VPS or through an SSH tunnel. This avoids exposing a database administration page to the internet during testing.

Does the docker group have administrator access?

Yes. A member can use the Docker socket to start privileged containers and access host files, which is effectively root-level control. Add only trusted users, or keep using sudo.

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

How to Install Docker Compose on Rocky Linux 10

Next Article

Windows Server Backup Software: Free Options Compared

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 ✨