How to Install Docker Compose and Run Your First Stack
Use Docker Compose to define and run a multi-container app from one YAML file. Build a WordPress + MySQL stack from scratch.

Docker Compose lets you define a whole application — web server, database, cache — in a single YAML file and start it all with one command. This guide confirms Compose is installed, explains the file format, and walks through launching a real WordPress + MySQL stack.
Compose comes with modern Docker
If you installed Docker from the official repository (see installing Docker on Ubuntu 24.04), Compose v2 is already there as a subcommand. Check it:
docker compose version
Note the space: it is docker compose, not the old hyphenated docker-compose. If the command is missing, install the plugin:
sudo apt install docker-compose-plugin -y
Anatomy of a compose file
A Compose file describes services (containers), volumes (persistent storage), and networks. Compose automatically puts all services on a shared network where they can reach each other by service name — so db becomes a hostname. That single feature is what makes multi-container apps painless.
Build a WordPress + MySQL stack
Create a project directory and a compose.yaml:
mkdir ~/wordpress && cd ~/wordpress
Put this in compose.yaml:
services:
db:
image: mysql:8.0
restart: unless-stopped
environment:
MYSQL_DATABASE: wordpress
MYSQL_USER: wpuser
MYSQL_PASSWORD: change-me-strong
MYSQL_ROOT_PASSWORD: change-me-root
volumes:
- db_data:/var/lib/mysql
wordpress:
image: wordpress:latest
restart: unless-stopped
depends_on:
- db
ports:
- "127.0.0.1:8080:80"
environment:
WORDPRESS_DB_HOST: db:3306
WORDPRESS_DB_USER: wpuser
WORDPRESS_DB_PASSWORD: change-me-strong
WORDPRESS_DB_NAME: wordpress
volumes:
- wp_data:/var/www/html
volumes:
db_data:
wp_data:
Notice WORDPRESS_DB_HOST: db:3306 — it reaches the database by the service name db. And the port is bound to 127.0.0.1 so it is not exposed publicly; you will put Nginx in front.
Start the stack
From the project directory:
docker compose up -d
Compose pulls both images, creates the volumes and network, and starts everything in the background. Watch it come up:
docker compose ps
docker compose logs -f

WordPress is now answering on 127.0.0.1:8080. Front it with an Nginx reverse proxy and free HTTPS from Certbot to serve it on your domain.
Everyday Compose commands
- Start / recreate:
docker compose up -d - Stop, keep data:
docker compose stop - Stop and remove containers:
docker compose down - Remove containers and volumes (deletes data):
docker compose down -v - Update images:
docker compose pull && docker compose up -d - Run a command in a service:
docker compose exec db mysql -u root -p
Persistence is in volumes
The named volumes db_data and wp_data keep your database and uploads even when containers are recreated. docker compose down is safe; only down -v deletes the volumes. Always back these up before major changes.
Where to go next
Compose is the foundation for self-hosting nearly anything: databases, n8n, Nextcloud, monitoring stacks, and more. For a point-and-click way to manage your Compose stacks, install Portainer.
Make dependencies reliable with health checks
depends_on only waits for a container to start, not for the service inside it to be ready — so an app can race ahead of a database that is still booting. Add a health check so Compose waits for genuine readiness:
db:
image: mysql:8.0
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
interval: 10s
retries: 5
Then depend on that health:
wordpress:
depends_on:
db:
condition: service_healthy
Keep secrets in an .env file
Rather than hard-coding passwords in the YAML, put them in a .env file beside the compose file; Compose substitutes variables automatically:
MYSQL_PASSWORD=change-me-strong
environment:
MYSQL_PASSWORD: ${MYSQL_PASSWORD}
Add .env to .gitignore so secrets never reach version control. This keeps one clean, shareable compose file with environment-specific values kept separate.
FAQ
What is the difference between docker compose up and start?
up creates containers (and networks/volumes) from your YAML, building or pulling as needed. start only restarts existing, stopped containers. Use up -d after editing the compose file; start to resume what you already created.
Should the file be named docker-compose.yml or compose.yaml?
Compose v2 prefers compose.yaml, but still reads the older docker-compose.yml. Either works — compose.yaml is the current convention.
How do services find each other?
By service name on the shared network Compose creates. In the example, WordPress connects to the database at host db — no IP addresses needed.
How do I safely change environment variables like passwords?
Edit the compose file, then docker compose up -d to recreate affected services. Note that a database's root password is only set on first creation; changing it later means updating it inside the running database, not just in the YAML.
Nxeon Docker hosting gives you a root-access VPS ready for Compose stacks on fast NVMe storage — spin up multi-container apps in minutes, with free migration help if you are moving one over.