Docker Compose lets you define multi-container applications in a single YAML file. Then it runs them for you instead of you starting each container by hand. Knowing how to install Docker Compose is the first step toward automating that process. From there, you write a docker-compose.yml file that describes your services, networks, and volumes. This docker compose tutorial covers installing Compose on Ubuntu and writing a working configuration. It also walks through deploying a real multi-container stack on a live server.
How to Install Docker Compose on Ubuntu
Checking Prerequisites
Most current Ubuntu servers already run a recent version of the Docker Engine. Compose itself, however, now ships as a plugin rather than a separate binary. Before you install it, confirm that Docker Engine actually runs on the VPS. Compose depends entirely on the Docker daemon to manage containers. Check this with docker –version and docker info. This check matters most on minimal or custom VPS images, since not every base image ships Docker by default. If either command fails, install Docker Engine first. The Compose plugin builds directly on top of it and doesn’t work without it. A fresh Linux VPS running Ubuntu typically needs both installed from scratch. Budget a few extra minutes for the base Docker install before moving to Compose itself.
Removing Conflicting Packages
Ubuntu’s default repositories sometimes include older packages named docker.io, docker-compose, or podman-docker, which conflict with the official Docker packages. Remove these before installing anything new, since leftover files from old installs occasionally interfere with the plugin. Run sudo apt-get remove docker.io docker-doc docker-compose podman-docker containerd runc to clear them out. It’s fine if apt doesn’t find some of these packages on the system. Apt simply skips packages it doesn’t find without stopping the command.
Installing the Compose Plugin
Update the package index first, then install the plugin through Docker’s official repository rather than Ubuntu’s default one. The official repository gets you the latest stable release, while Ubuntu’s own packages often lag several versions behind. Running these steps against a fresh Ubuntu 22.04 or 24.04 image on Contabo works without any modification. Docker publishes packages for both releases. Run the following commands in order:
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg
sudo install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
sudo chmod a+r /etc/apt/keyrings/docker.gpg
echo \
"deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
$(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null
sudo apt-get update
sudo apt-get install docker-compose-plugin
docker compose versionEach command builds on the last. The first two update the system and install tools Docker’s repository needs. The third and fourth add Docker’s signing key so apt can verify the packages it downloads. The fifth line registers the repository itself, matching it to the exact Ubuntu release running on the VPS. The final two lines refresh the package index and install the Compose plugin. Installing the plugin also pulls in the rest of the Docker Engine stack as a dependency.
Verifying the Installation
Confirm the install worked by checking the version output. A successful install prints something like Docker Compose version v2.29.0. Pay attention to the syntax here. Modern Compose runs as docker compose, two words, rather than the older docker-compose with a hyphen. Both forms coexist because systems that installed the older standalone binary earlier can keep using it. The plugin form, though, is the version Docker maintains and recommends going forward, so new installs should always use it. Add the current user to the docker group afterward with sudo usermod -aG docker $USER. Then log out and back in. This lets Compose commands run without sudo on every invocation.
Writing Your First docker-compose.yml
A docker-compose.yml file describes an entire application stack in a single place. It defines which containers to run, how they connect to each other, and what data they share between restarts. This docker compose tutorial builds a simple two-service stack: a web application and a database. That pairing shows how the pieces fit together without adding unnecessary complexity. Compose reads the file and creates a private network for the services. It then starts every container in the order their dependencies require.
Defining Services
Every docker-compose.yml file starts with a services block, where each key names one container. This same file structure applies to almost any stack. It works whether the stack pairs a web server with a database, a cache, or a background worker. The following example runs an Nginx web server alongside a PostgreSQL database:
services:
web:
image: nginx:latest
ports:
- "80:80"
depends_on:
- db
db:
image: postgres:16
environment:
POSTGRES_PASSWORD: examplepassword
volumes:
- db_data:/var/lib/postgresql/dataThe web service maps port 80 on the VPS to port 80 inside the container. That makes the site reachable from a browser. The depends_on key tells Compose to start the database before the web server. It doesn’t, however, wait for PostgreSQL to finish initializing before starting Nginx. The db service passes a password through an environment variable instead of hardcoding it into an image. It also stores its data in a named volume rather than inside the container itself. That way, the data survives a container restart.
Adding Volumes and Networks
Volumes need their own declaration outside the services block, so Compose knows to create and manage them. Add a top-level volumes key and list db_data underneath it:
volumes:
db_data:Compose also creates a default network automatically, so both containers can reach each other by service name. The web container, for example, can connect to the database using db as the hostname instead of an IP address. Custom networks become useful once a stack grows beyond a couple of services. They let you isolate groups of containers from each other. The default network, though, handles most beginner setups without any extra configuration. Add a custom network only once the stack needs that extra isolation.
Adding an Environment File
Hardcoding passwords directly in docker-compose.yml works for a quick test. It doesn’t scale well once the file goes into version control. Move values like POSTGRES_PASSWORD into a separate .env file in the same directory. Reference them from the compose file with a variable like ${POSTGRES_PASSWORD}. Compose reads this file automatically without any extra configuration. Add .env to .gitignore so real credentials never end up in a shared repository. This same pattern works for API keys, database names, and any other value that changes between environments.
Running the Stack
Save the file as docker-compose.yml in the project directory. Then run docker compose up -d to start every service in the background. Compose pulls any missing images, creates the network and volume, and launches the containers in dependency order. Check that everything runs correctly with docker compose ps, which lists each service alongside its current status. View logs from a specific service with docker compose logs web, or add -f to follow them in real time. Stop the entire stack with docker compose down when you no longer need it. Running it without additional flags leaves named volumes intact, so restarting the stack later doesn’t lose any data. Add -v only when you also want to remove the volumes.
Scaling and Updating Services
Compose can also run more than one instance of a service at once, but port mappings need care first. Running docker compose up -d –scale web=3 against the compose file shown earlier crashes immediately. All three containers try to bind to the host’s port 80, and only one can succeed. Scaling a web service on a single server means removing the ports mapping from the web container first. A reverse proxy, such as Caddy or Traefik, then sits in front of the containers and load-balances incoming traffic across them. This approach works well for stateless services like a web server, but it doesn’t fit a database. Running multiple instances of PostgreSQL this way would cause conflicts over the same data. To update a running stack, first change the image version in the compose file. Then run docker compose pull, then docker compose up -d. Compose then recreates only the containers that actually changed, leaving the rest running untouched.
Why Run Docker Compose on an Unmanaged Contabo VPS
Running Docker Compose on a VPS puts the entire stack under direct control. Nothing relies on a platform’s built-in orchestration layer. The docker-compose.yml file, the environment variables, and the volumes all belong to whoever writes them. Nothing about the configuration depends on a specific hosting provider’s tooling or dashboard. None of this happens through a proprietary control panel feature, which keeps the setup portable by design. A stack built this way runs the same way everywhere. That includes a laptop, a cloud server, or any other machine with Docker installed. That portability matters more than it seems at first. The same file works in development, staging, and production without rewriting anything.
What Contabo Provides, and What It Doesn’t
Contabo’s role in this setup stays limited to supplying the virtual machine itself. That means the CPU, memory, storage, and network connection the containers run on. It doesn’t manage the compose file, monitor the containers, or restart a service that crashes overnight. Every decision about which images to run and how services connect remains up to whoever wrote the configuration. The same goes for how data persists. That separation keeps things simple and predictable, since nothing hides behind a provider-specific dashboard or a proprietary deployment format. Moving the same docker-compose.yml file to a different VPS provider takes little effort. Copy it over and run docker compose up again. If a container crashes at 3 a.m., Compose alone won’t restart it either. Add a restart policy such as restart: unless-stopped to each service to handle that automatically.
Backups follow the same logic. Contabo can snapshot the VPS as a whole, but it doesn’t know which volumes hold data worth keeping. Setting up a backup routine falls to whoever owns the compose file. That applies to the Postgres volume or any other stateful service. The hosting layer doesn’t handle that automatically.
FAQ: Docker Compose on a VPS
How do I install Docker Compose on a VPS?
Installing Docker Compose on a VPS follows the same steps as installing it on any Ubuntu machine. Update the package index, add Docker’s official repository, then install the docker-compose-plugin package through apt. Running docker compose version afterward confirms the install worked and shows which version is active. Most current VPS images already include the Docker Engine. The only extra step is adding the Compose plugin on top of it. Anyone searching for how to install Docker Compose on a fresh Contabo VPS can follow this same sequence of commands. None of it requires provider-specific changes. You still need to configure a local firewall rule separately to allow inbound traffic on the required ports. Installing Compose alone doesn’t open anything on the network.
What is Docker Compose used for?
Docker Compose defines and runs multi-container applications from a single configuration file. That replaces separate docker run commands for every container. It handles the networking between services automatically and manages named volumes for persistent data. It also starts containers in the correct dependency order. Development teams use it to spin up an entire local environment with one command. That environment might include a database, a cache, and an application server. This docker compose tutorial focuses on one exact use case. It gets a small multi-service stack running quickly on a real server. Beyond local development, teams also use it for small production deployments. There, a full orchestration platform would add more overhead than value. It also makes onboarding new team members faster. Running the whole environment takes one command instead of a long setup document.
Do I need Docker Swarm if I already use Docker Compose?
Not necessarily. Docker Compose handles single-host deployments well, running every container on one VPS with straightforward networking between services. Docker Swarm exists for a different problem: distributing containers across multiple machines and keeping them running if one node fails. A single production host running a handful of services rarely needs that extra complexity. Compose file syntax also overlaps heavily with Swarm’s stack format, so switching later doesn’t mean starting from scratch. Most small to medium projects stay on Compose indefinitely. They only look at Swarm, or a heavier tool like Kubernetes, once traffic genuinely outgrows a single server. For most Contabo VPS users running a handful of services on one machine, Compose alone covers everything Swarm would offer. It does this without the added operational overhead.