Install Docker Compose on Debian 13 by adding Docker’s signed repository, installing the Compose v2 plugin, and launching a small Nginx and PostgreSQL stack. You can complete the setup in about ten minutes and verify it entirely from your SSH session without exposing the database to the internet.
Prerequisites
- A 64-bit Debian 13 (Trixie) 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. - Permission to download packages from Docker’s APT repository and images from Docker Hub.
- A VPSLake Linux VPS if you need a Debian server with root access.
- No domain is needed for this private test stack. Use a domain later if you put a TLS reverse proxy in front of a public application.
The commands use sudo for a regular SSH account. If you are logged in as root, remove sudo from the commands.
Step 1: Confirm Debian 13 and refresh APT
Docker’s repository must be configured for Trixie’s trixie codename, so confirm the release and architecture before changing package sources.
grep -E '^(PRETTY_NAME|VERSION_CODENAME)=' /etc/os-release
dpkg --print-architecture
sudo apt update
You should see Debian 13 and a supported architecture such as amd64 or arm64:
PRETTY_NAME="Debian GNU/Linux 13 (trixie)"
VERSION_CODENAME=trixie
amd64
If apt update reports a broken mirror, DNS error, or unsigned repository, fix that first. Compose cannot install until Debian’s package metadata is healthy.
Step 2: Add Docker’s official repository
The keyring and deb822 source file let APT verify Docker packages without the deprecated apt-key approach.
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: trixie
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF
sudo apt update
Check that APT has a Docker Engine candidate for Trixie:
apt-cache policy docker-ce
The candidate should come from download.docker.com and reference trixie. Docker’s Debian installation documentation lists Trixie as a supported release and documents this repository method.
Step 3: Install Docker Engine and the Compose plugin
Compose v2 is a Docker CLI plugin, so install it alongside the Engine instead of using the obsolete hyphenated docker-compose command.
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
The service check should print active, followed by Docker client/server versions and a Compose v2 version. Exact version numbers change as Docker publishes updates:
active
Client 29.x.y / Server 29.x.y
Docker Compose version v2.x.y
If Docker Engine is already installed, run the last three commands first. You only need the repository and package installation when docker compose version is missing or the Engine is not present. Avoid mixing an old docker.io installation with docker-ce; plan any migration before removing packages that may own existing containers or volumes.
Step 4: Let your SSH user run Docker
The Docker socket is root-owned by default. Adding your account to the docker group avoids repeated sudo, but that group grants root-equivalent control over the server.
sudo groupadd --force docker
sudo usermod -aG docker "$USER"
Start a new SSH session so the group membership is loaded, then check access:
id -nG
docker info --format 'Server {{.ServerVersion}}'
The group list should include docker, and docker info should print the server version. Until reconnecting, use sudo docker compose ... or run newgrp docker for the current shell.
Step 5: Create a two-service Compose project
A Compose file describes the services, network, storage, and startup relationship in one place. This example keeps Nginx on the VPS loopback address and gives PostgreSQL a private network plus a named data volume.
Create the project and a page for Nginx:
mkdir -p ~/compose-debian13/site
cd ~/compose-debian13
printf '%s\n' 'Docker Compose on Debian 13 is running.' > site/index.html
Create an environment file for the database password. Replace YOUR_STRONG_POSTGRES_PASSWORD with a long, unique password before starting the stack; it is a placeholder, not a usable credential.
tee .env >/dev/null <<'EOF'
POSTGRES_PASSWORD=YOUR_STRONG_POSTGRES_PASSWORD
EOF
chmod 600 .env
Now create compose.yaml:
services:
web:
image: nginx:alpine
ports:
- "127.0.0.1:8090:80"
volumes:
- ./site:/usr/share/nginx/html:ro
depends_on:
db:
condition: service_healthy
db:
image: postgres:16-alpine
environment:
POSTGRES_DB: appdb
POSTGRES_USER: appuser
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:?Set POSTGRES_PASSWORD in .env}
volumes:
- pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U appuser -d appdb"]
interval: 5s
timeout: 5s
retries: 10
volumes:
pg_data:
The :ro mount stops Nginx from changing the page, while pg_data keeps PostgreSQL data when containers are recreated. The database has no ports entry, so it is not published on the VPS network. See Docker’s Compose file reference when adding application-specific services or secrets.
Step 6: Validate and start the stack
Validate the rendered YAML before pulling images; this catches indentation errors and a missing password before containers are created.
docker compose config --quiet
docker compose pull
docker compose up -d
docker compose ps
config --quiet should return no output and status 0. The first image pull can take a few minutes. Both services should then show Up or running, with the web service bound to 127.0.0.1:8090 and the database marked healthy:
NAME SERVICE STATUS PORTS
compose-debian13-web-1 web Up 127.0.0.1:8090->80/tcp
compose-debian13-db-1 db Up (healthy)
Verify Docker Compose on Debian 13
Check the HTTP response, database readiness, and recent logs. These checks prove that Compose installed correctly and that both services can start together.
curl --fail --silent --show-error http://127.0.0.1:8090
docker compose exec db pg_isready -U appuser -d appdb
docker compose logs --tail 20 web db
The first command should print the page text, and PostgreSQL should report that it is accepting connections:
Docker Compose on Debian 13 is running.
/var/run/postgresql:5432 - accepting connections
The port is intentionally bound to loopback, so a browser on your own computer cannot reach it directly. If you need to inspect it remotely, use an SSH tunnel or put a TLS reverse proxy in front of the application; do not publish PostgreSQL. Docker notes that published container ports can bypass ordinary UFW rules, so use a deliberate DOCKER-USER policy before exposing a production service. VPSLake’s UFW firewall generator can help draft the host rules.
Troubleshooting
docker: 'compose' is not a docker command
The Compose CLI plugin is missing, or the shell is using a different Docker installation. Refresh APT and install the plugin from Docker’s repository:
sudo apt update
sudo apt install -y docker-compose-plugin
docker compose version
Use docker compose with a space. The standalone docker-compose binary is a separate legacy installation and is not required here.
permission denied while trying to connect to the Docker daemon socket
Your current SSH session has not loaded the new docker group, or the account was not added to it. Reconnect, run id -nG, and retry; sudo docker compose ... is a temporary workaround.
E: Package 'docker-ce' has no installation candidate
APT is not reading the Trixie repository, usually because the source file has the wrong suite or the last update failed. Inspect the file and refresh the index:
cat /etc/apt/sources.list.d/docker.sources
sudo apt update
apt-cache policy docker-ce
The file must contain Suites: trixie, and apt update must finish without an error. Do not replace trixie with bookworm; that installs packages for the wrong Debian release.
dependency failed to start: container db is unhealthy
Read the database log and confirm that .env contains POSTGRES_PASSWORD with no surrounding quotes or spaces:
docker compose logs db
docker compose config
If this project has never started, correct the value and run docker compose up -d again. PostgreSQL stores its initial password in the named volume, so changing .env does not change credentials in an existing database. Only for disposable test data, docker compose down -v removes that volume; never run it when the database contains data you need.
Hardening
- Keep
.envmode600, never commit it to source control, and move production secrets to a managed secret store. - Keep PostgreSQL unexposed and leave the web service on
127.0.0.1until TLS, authentication, and firewall policy are ready. - Back up
pg_dataand test restoration before treating the stack as production data. - Pin production images to reviewed tags or digests, then update deliberately with
docker compose pullanddocker compose up -d.
FAQ
Do I need the old docker-compose command on Debian 13?
No. Docker Compose v2 is provided as the docker compose CLI plugin, which is the maintained syntax used by this guide. The hyphenated command belongs to the legacy standalone tool and can create confusion when both installations exist.
Can I open the Nginx page from my laptop?
Not with the sample binding, because 127.0.0.1 refers to the VPS itself. You can create an SSH tunnel to port 8090 for private testing, or publish only a protected reverse proxy after adding TLS and an appropriate firewall policy.
Does docker compose down delete PostgreSQL data?
Regular down removes the containers and Compose network but keeps the named pg_data volume. The -v option also deletes that volume, so reserve it for a disposable reset after confirming that no data is needed.
How do I stop or restart the project later?
From ~/compose-debian13, use docker compose stop for a temporary stop and docker compose start to bring the existing containers back. Use docker compose up -d after changing the Compose file so Compose can recreate services when necessary.