> ## Documentation Index
> Fetch the complete documentation index at: https://help.freakhosting.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Docker Compose Tutorial: Run Multi-Container Apps

> Docker Compose tutorial: define your containers in compose.yaml, start them with docker compose up -d, then check, update and stop them with one command.

Docker Compose runs several containers from one file. Describe your services, ports and volumes in `compose.yaml`, then run `docker compose up -d` in that folder to start everything in the background. Use `docker compose ps` to check it, `docker compose pull && docker compose up -d` to update, and `docker compose down` to stop and remove the containers.

| Difficulty | Time | Works on | Needs |
| - | - | - | - |
| Beginner | 15 minutes | Any Linux server with Docker Engine | [Docker with the Compose plugin](https://help.freakhosting.com/portals/other/how-to-install-docker.md) |

## Before you start

* Install Docker and the `docker-compose-plugin` package. Check with `docker compose version`.
* Run the commands as a user in the `docker` group, or add `sudo` in front of each one.
* Use `docker compose` (with a space). The old `docker-compose` (with a hyphen) is Compose v1 and is no longer updated.

## What is a compose.yaml file?

`compose.yaml` is a YAML file that lists your **services** (containers), the **image** each one runs, and its **ports**, **environment variables** and **volumes** (storage that survives when the container is replaced). Compose looks for `compose.yaml` (preferred) or `compose.yml` in the current folder. It still reads the older `docker-compose.yml` name.

You don't need a `version:` line. Compose ignores it and prints `the attribute 'version' is obsolete`.

## How to create your first Compose app

This example runs WordPress with a MariaDB database.

**1. Make a project folder**

```bash theme={null}
mkdir -p ~/wordpress && cd ~/wordpress
```

The folder name becomes the project name, so containers are called `wordpress-wordpress-1` and `wordpress-db-1`.

**2. Put secrets in a .env file**

Create `.env` next to `compose.yaml`:

```ini theme={null}
DB_PASSWORD=Change-Me-2026
```

Compose reads `.env` from the project folder and fills in `${DB_PASSWORD}`. Run `chmod 600 .env` so other users can't read it.

**3. Write compose.yaml**

```yaml theme={null}
services:
  wordpress:
    image: wordpress:latest
    restart: unless-stopped
    ports:
      - "8080:80"
    environment:
      WORDPRESS_DB_HOST: db
      WORDPRESS_DB_USER: wordpress
      WORDPRESS_DB_PASSWORD: ${DB_PASSWORD}
      WORDPRESS_DB_NAME: wordpress
    volumes:
      - wordpress:/var/www/html
    depends_on:
      - db

  db:
    image: mariadb:11.4
    restart: unless-stopped
    environment:
      MARIADB_DATABASE: wordpress
      MARIADB_USER: wordpress
      MARIADB_PASSWORD: ${DB_PASSWORD}
      MARIADB_RANDOM_ROOT_PASSWORD: "1"
    volumes:
      - db:/var/lib/mysql

volumes:
  wordpress:
  db:
```

Services reach each other by service name, so WordPress connects to the database at `db`. The database has no `ports:` entry, so it is not reachable from the internet.

**4. Check the file**

```bash theme={null}
docker compose config
```

This prints the final configuration with variables filled in, or an error with the line number.

**5. Start the app**

```bash theme={null}
docker compose up -d
```

`-d` (detached) runs the containers in the background. Open `http://203.0.113.10:8080` with your server's IP.

## How to check it worked

```bash theme={null}
docker compose ps
```

```text theme={null}
NAME                    IMAGE              COMMAND                  SERVICE     CREATED         STATUS         PORTS
wordpress-db-1          mariadb:11.4       "docker-entrypoint.s…"   db          2 minutes ago   Up 2 minutes   3306/tcp
wordpress-wordpress-1   wordpress:latest   "docker-entrypoint.s…"   wordpress   2 minutes ago   Up 2 minutes   0.0.0.0:8080->80/tcp, [::]:8080->80/tcp
```

## Docker Compose commands you will use

Run these inside the project folder.

| Task | Command |
| - | - |
| Start or apply changes | `docker compose up -d` |
| List the containers | `docker compose ps` |
| Follow logs | `docker compose logs -f` (one service: `docker compose logs -f db`) |
| Last 50 log lines | `docker compose logs --tail 50` |
| Open a shell in a service | `docker compose exec wordpress bash` |
| Restart one service | `docker compose restart wordpress` |
| Stop without removing | `docker compose stop` |
| Stop and remove containers | `docker compose down` |
| Remove containers and volumes | `docker compose down -v` |

> [!WARNING]
>
> `docker compose down -v` deletes the named volumes, which means your database and uploaded files. Plain `docker compose down` keeps them. Take a backup before using `-v`.

## How to update containers with Docker Compose

```bash theme={null}
cd ~/wordpress
docker compose pull
docker compose up -d
```

`pull` downloads newer images. `up -d` recreates only the containers whose image or settings changed. Your volumes stay. Run `docker image prune` afterwards to remove the old images; see [clean up Docker disk space](https://help.freakhosting.com/portals/other/how-to-clean-up-docker-disk-space.md).

## How to keep a service private

Ports published with `"8080:80"` bypass `ufw` and `firewalld`. To reach a service only through a [reverse proxy](https://help.freakhosting.com/portals/other/how-to-set-up-nginx-as-a-reverse-proxy.md) on the same server, bind it to localhost:

```yaml theme={null}
    ports:
      - "127.0.0.1:8080:80"
```

## Frequently asked questions

### Do containers start again after a reboot?

Yes, if the service has `restart: unless-stopped` or `restart: always` and the Docker service is enabled at boot. You don't need a cron job or systemd unit.

### Where are my volumes stored?

Named volumes live under `/var/lib/docker/volumes/`. List them with `docker volume ls`. To use a folder you choose, write a bind mount such as `./data:/var/lib/mysql` instead.

### Does depends\_on wait until the database is ready?

No. Plain `depends_on` only sets the start order. WordPress connects on each page request, so it works as soon as the database has finished starting. For apps that need the database ready at start-up, add a `healthcheck` to the database and use the long `depends_on` form with `condition: service_healthy`.

### Can I use the same compose.yaml on another server?

Yes. Copy the project folder, including `.env`, and run `docker compose up -d`. Copy volume data separately.

## Related guides

> [card] [VPS and dedicated server guides](https://help.freakhosting.com/portals/other/vps-and-dedicated-server-guides.md)
>
> Every guide in this section.

> [card] [Install Docker](https://help.freakhosting.com/portals/other/how-to-install-docker.md)
>
> Docker Engine and the Compose plugin.

> [card] [Docker commands cheat sheet](https://help.freakhosting.com/portals/other/essential-docker-commands.md)
>
> Containers, images, volumes and logs.

> [card] [Install Portainer](https://help.freakhosting.com/portals/other/how-to-install-portainer.md)
>
> Deploy Compose stacks in a web UI.

> [card] [Nginx reverse proxy](https://help.freakhosting.com/portals/other/how-to-set-up-nginx-as-a-reverse-proxy.md)
>
> Put a domain and HTTPS in front of a container.

***

> [!NOTE]
>
> **Checked against the official [Docker Compose documentation](https://docs.docker.com/compose/)** on 4 November 2026; `compose.yaml` validated and Compose commands tested on Ubuntu 24.04 LTS with Docker Compose v5.6.1. Commands can differ on other releases.

> [!NOTE]
>
> ### Need Extra Help?
>
> If you encounter any issues, our support team is ready to assist:
>
> * **Live Chat:** Quick assistance via our website.
> * **Support Ticket:** [Open a Ticket](https://freakhosting.com/clientarea/submitticket.php)
> * **Discord:** [Join our Community](https://discord.gg/freakhosting)
> * **Email:** [support@freakhosting.com](mailto:support@freakhosting.com)

> [!TIP]
>
> ### Save on Your Hosting
>
> Ready to upgrade your hosting? Use code **KB20** at checkout for **20% off** your first order!

***

**Last Updated:** November 2026


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.