# How to use Docker Compose

Docker Compose lets you describe a multi-container application - for example an app plus its
database - in a single `compose.yaml` file and start it with one command. On TurboStack the modern
**`docker compose`** (v2) is available once Docker is enabled, and TurboStack still
reverse-proxies public traffic to your app through Nginx.

Use Compose instead of a single [`docker run`](run-a-container.md) when your app is more than one
container, or when you want its configuration version-controlled in a file.

## 1. Enable Docker for the application

Turn on Docker under **Configure application > Technologies > Docker** (see
[Configure Docker](configure.md)). Every system user with Docker enabled is added to the `docker`
group and can run the `docker` and `docker compose` commands over
[SSH](../../platform/hosts/ssh.md).

## 2. Write a `compose.yaml`

Create a `compose.yaml` in your application directory (for example `~/app`). Publish only the
front-facing port, and bind it to **localhost** so it is reachable only through Nginx; internal
services (such as a database) need no published ports because containers reach each other by service
name on the Compose network.

```yaml
services:
  web:
    image: your-image:1.2
    restart: unless-stopped
    ports:
      - "127.0.0.1:8080:80"   # published to localhost; Nginx proxies to this
    environment:
      DATABASE_URL: "postgres://appuser@db:5432/appdb"
    depends_on:
      - db

  db:
    image: postgres:17
    restart: unless-stopped
    environment:
      POSTGRES_USER: appuser
      POSTGRES_DB: appdb
      POSTGRES_PASSWORD: change-me
    volumes:
      - db-data:/var/lib/postgresql/data
    # no ports: only reachable by other services (here, "web") on the Compose network

volumes:
  db-data:
```

- **Pin image tags** (`your-image:1.2`, `postgres:17`) rather than `latest`, so deployments are
  reproducible.
- **`restart: unless-stopped`** brings a container back after a crash or a server reboot.
- Keep data you need to survive a container rebuild in a **named volume** (`db-data` above), never in
  the container's writable layer.
- Store secrets in an **`.env` file** next to the compose file (Compose reads it automatically) rather
  than committing them.

## 3. Start and manage the stack

Run these from the directory that holds `compose.yaml`:

```bash
docker compose up -d      # create and start everything, in the background
docker compose ps         # what is running
docker compose logs -f    # follow the logs (Ctrl+C to stop watching)
docker compose pull       # fetch newer images...
docker compose up -d      # ...then re-create containers with them
docker compose down       # stop and remove the containers (keeps named volumes)
```

> [!WARNING]
> `docker compose down --volumes` also deletes the named volumes - and the data in them. Leave off
> `--volumes` unless you really want to wipe the data.

## 4. Publish it through TurboStack

Point the [reverse proxy](../reverse-proxy/configure.md) at the port you published so Nginx serves the
app on your domain with HTTPS:

```yaml
docker_enabled: true
proxy_enabled: true
proxy_upstream_port: 8080   # the host port published by the web service
```

[Publish](../../platform/hosts/publishing.md) the change to apply it.

## Keep the stack running

`restart: unless-stopped` already restarts your containers after a crash or reboot. To manage the
whole stack as one service - so it is guaranteed to launch and you can start and stop it cleanly -
wrap it in a **user systemd service**. Create `~/.config/systemd/user/myapp.service`:

```ini
[Unit]
Description=My Compose app
After=network-online.target
StartLimitIntervalSec=0

[Service]
Type=oneshot
RemainAfterExit=yes
WorkingDirectory=%h/app
ExecStart=docker compose up -d
ExecStop=docker compose down
Restart=on-failure
RestartSec=15s

[Install]
WantedBy=default.target
```

```bash
systemctl --user enable --now myapp.service
systemctl --user status myapp.service
```

TurboStack keeps user services running after you log out, so the stack survives reboots. For the
`systemd --user` basics, see [Run with a process manager](../nodejs/run-with-process-manager.md).

> [!NOTE]
> On modern hosts the command is **`docker compose`** (v2, a space). Only very old servers have the
> legacy **`docker-compose`** (with a hyphen); the options are otherwise the same.

## Related

- [Run a single Docker container](run-a-container.md)
- [Configure Docker](configure.md)
- [Configure Reverse Proxy](../reverse-proxy/configure.md)
- [Run with a process manager](../nodejs/run-with-process-manager.md)
- [SSH access](../../platform/hosts/ssh.md)
- [What is Docker?](what-is.md)
