Install Docker Compose on Debian 12 using Docker’s current Compose v2 plugin, then launch and manage a small web-and-cache stack. The setup takes about ten minutes and leaves you with commands you can reuse for real multi-container applications.
Prerequisites
- A 64-bit Debian 12 (Bookworm) VPS with at least 2 GB of RAM and 10 GB of free disk space.
- SSH access as
root, or a normal account with workingsudoprivileges. - Outbound HTTPS access to Docker’s package repository and Docker Hub.
- A VPSLake Linux VPS if you need a Debian server with root access.
- A domain is not required for this local verification example. Add one later when putting a TLS reverse proxy in front of a public service.
The commands use sudo for a normal SSH account. If you are logged in as root, remove sudo and do not add a second sudo prefix.
Step 1: Confirm Debian 12 and update APT
Docker’s repository must use Bookworm, so confirm the release before changing APT sources.
grep -E '^(PRETTY_NAME|VERSION_CODENAME)=' /etc/os-release
dpkg --print-architecture
sudo apt update
Expected output includes VERSION_CODENAME=bookworm and a supported architecture such as amd64 or arm64:
PRETTY_NAME="Debian GNU/Linux 12 (bookworm)"
VERSION_CODENAME=bookworm
amd64
Do not continue while apt update reports a broken mirror, DNS failure, or expired repository. Docker’s packages cannot fix an unhealthy Debian package configuration.
Step 2: Add Docker’s signed Debian repository
The dedicated keyring and deb822 source file avoid the deprecated apt-key workflow and let APT verify Docker packages.
sudo apt install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
sudo tee /etc/apt/sources.list.d/docker.sources >/dev/null <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
Confirm that APT can see a Docker Engine candidate for Bookworm:
apt-cache policy docker-ce
The candidate should come from download.docker.com and show bookworm in its repository line. Docker lists the supported Debian releases and the repository method in its official Debian installation guide.
Step 3: Install Docker Engine and Compose v2
Compose is installed as docker compose, a subcommand provided by Docker’s Compose plugin; the old standalone docker-compose command is not needed.
sudo apt 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, matching client and server versions, and a Compose v2 version. Exact numbers change as Docker publishes updates:
active
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y
If Docker was already installed, you can skip the repository and package commands, but still run docker compose version. If the command is missing, install docker-compose-plugin from the same Docker repository.
Step 4: Allow a non-root account to run Compose
Docker’s Unix socket is root-owned by default. The docker group removes repeated sudo prompts, but membership grants root-equivalent control over the VPS.
Run this for the Linux account that will manage the project:
sudo groupadd --force docker
sudo usermod -aG docker "$USER"
Disconnect from SSH and reconnect so the new group is loaded. Then check the group and daemon connection:
id -nG
docker info --format 'Server {{.ServerVersion}}'
The first command should include docker. Until you reconnect, prefix Docker commands with sudo or run newgrp docker in the current shell. Treat this group as administrative access; use rootless Docker when you need a stronger boundary.
Step 5: Create a Compose project
Compose reads a YAML file to define services, storage, and networking. This example runs Nginx on loopback and Redis with a named volume, so it demonstrates two services without exposing a new public port. The Compose file reference documents the available keys when you extend this example.
Create a project directory and a page for Nginx:
mkdir -p ~/compose-demo/site
cd ~/compose-demo
printf '%s\n' 'Docker Compose on Debian 12 is working.' > site/index.html
Create compose.yaml:
tee compose.yaml >/dev/null <<'YAML'
services:
web:
image: nginx:alpine
ports:
- "127.0.0.1:8080:80"
volumes:
- ./site:/usr/share/nginx/html:ro
depends_on:
- cache
cache:
image: redis:7-alpine
command: ["redis-server", "--appendonly", "yes"]
volumes:
- redis_data:/data
volumes:
redis_data:
YAML
The web service maps only to 127.0.0.1, while cache stays on Compose’s private network. The named volume keeps Redis data outside the container lifecycle; the :ro suffix prevents Nginx from changing the website files.
Step 6: Validate and start the stack
Validate the fully resolved configuration before downloading images or creating containers.
docker compose config
docker compose pull
docker compose up -d
config should print the two services and redis_data without a YAML error. The first pull may take a few minutes; up -d returns control of the terminal while both services run in the background.
Check the service state:
docker compose ps
Expected output is similar to this, with both services showing Up or running:
NAME IMAGE SERVICE STATUS PORTS
compose-demo-web-1 nginx:alpine web Up 127.0.0.1:8080->80/tcp
compose-demo-cache-1 redis:7-alpine cache Up
The generated container names can differ if you choose another project directory. Use the SERVICE column when selecting a service in later commands.
Verify Docker Compose on Debian 12
Verify both the HTTP response and the Redis service, then inspect the project logs if either check fails.
curl --fail --silent --show-error http://127.0.0.1:8080
docker compose exec cache redis-cli ping
docker compose logs --tail 20
The first two commands should return the page text and PONG:
Docker Compose on Debian 12 is working.
PONG
The web port is deliberately reachable only from the VPS itself. To make a service public, change the mapping to "8080:80", allow that exact port in your firewall, and put a TLS reverse proxy in front of it. Docker warns that published ports can bypass ordinary UFW rules; plan the DOCKER-USER policy before exposing production services. VPSLake’s UFW firewall generator can help draft a host rule set.
Troubleshooting
docker: 'compose' is not a docker command
The Compose plugin is missing, or the CLI is using a different installation than the one managed by Docker’s repository. Check the package and install it with APT:
apt-cache policy docker-compose-plugin
sudo apt install -y docker-compose-plugin
docker compose version
Use docker compose with a space. The legacy hyphenated docker-compose binary is a separate tool and is not required for this guide.
permission denied while trying to connect to the Docker daemon socket
Your current SSH session does not yet know about the docker group, or the account was not added to it. Reconnect, run id -nG, and retry. sudo docker compose ... is a temporary workaround while you arrange the session change.
E: Package 'docker-ce' has no installation candidate
APT has not loaded the Docker Bookworm index, often because the source file has the wrong codename or an earlier apt update failed. Inspect the source and refresh metadata:
cat /etc/apt/sources.list.d/docker.sources
sudo apt update
apt-cache policy docker-ce
The file must contain Suites: bookworm, and apt update must finish without an error. Do not mix a Debian docker.io installation with Docker’s docker-ce packages; remove or account for conflicting packages first.
curl: (7) Failed to connect or the web check returns connection refused
The web service may still be pulling its image, may have exited, or the port is bound only to the VPS loopback address. Review the state and logs:
docker compose ps
docker compose logs web
ss -ltnp | grep ':8080'
If web is not running, the log normally identifies a bad mount or configuration. If you are testing from your own computer, 127.0.0.1:8080 is the VPS’s loopback—not your computer’s; use an SSH tunnel or configure a protected public reverse proxy instead.
Hardening
- Keep Docker’s daemon on its Unix socket. Never expose an unauthenticated Docker API on port 2375.
- Keep application ports on loopback until TLS, authentication, and firewall policy are ready.
- Back up important named volumes and use
docker compose down -vonly when you intentionally want to delete the project’s stored data. - Pin production images to reviewed tags or digests, and update them deliberately with
docker compose pullfollowed bydocker compose up -d.
FAQ
Is Docker Compose already included with Docker on Debian 12?
It is included when you install Docker Engine from Docker’s official APT repository together with docker-compose-plugin. Debian’s separate packages or an older manual Docker install may not include the plugin, so verify with docker compose version.
What is the difference between docker compose up and docker compose start?
up creates or recreates the services, networks, and volumes described by the Compose file, then starts them. start only starts existing stopped containers and does not create a missing project, so use up -d after changing configuration or setting up a project for the first time.
Does docker compose down delete the Redis data?
No. The regular down command removes the containers and default network but leaves the named redis_data volume. docker compose down -v also removes that volume, so use the -v option only when resetting the data is intentional.
How do Compose services find each other?
Compose puts services on a project network and provides DNS names matching their service keys. A container can reach the Redis service at cache:6379; it should not use the host’s localhost, which points back to the calling container.