---
title: "Deploy a Docker Compose app | Pethost Docs"
description: "Deploy a Docker Compose app as written: put your compose.yaml online with one command, the panel or your AI agent, with HTTPS, volumes and rollbacks."
---

[Docs](https://pethost.dev/docs/) · Deploy

# Deploy a Docker Compose app

Updated 29 October 2026 · 9 min read

You have a compose.yaml, or a docker-compose.yml, that works on your laptop, and you want it online at an HTTPS address. On Pethost the file runs as written: `pethost deploy` in its folder builds the images, starts every service and gives the web service its address. Here Docker's own Compose quickstart goes online that way, in 28 seconds.

**In short**

- **One command:** `pethost deploy` in the folder with compose.yaml, or the panel, or your AI agent.
- **The address:** a published port that answers HTTP gets `https://<project>.<you>.pethost.app`. A service with no `ports:` stays private.
- **Nothing is converted:** what Pethost will not run is refused before anything starts, with what to write instead.
- **Data and secrets:** named volumes outlive every deploy and are backed up nightly. Secrets go into the project's `.env`.
- **The price:** €9.99 a month for as many projects as fit. There is no free tier.

## Deploy a Docker Compose app with one command

### Step 1: Install pethost

On macOS and Linux:

```
curl -fsSL https://pethost.dev/install.sh | sh
```

On Windows, in PowerShell:

```
irm https://pethost.dev/install.ps1 | iex
```

You need a plan; the first deploy signs you in through your browser.

### Step 2: Go to the folder that has compose.yaml

Here it is Docker's [Compose quickstart](https://docs.docker.com/compose/gettingstarted/), in a folder named `counter`: a Flask app that counts visits in Redis, its Dockerfile, and these two files:

compose.yaml

```
services:
  web:
    build: .
    ports:
      - "${APP_PORT}:5000"
    environment:
      - REDIS_HOST=${REDIS_HOST}
      - REDIS_PORT=${REDIS_PORT}

  redis:
    image: redis:alpine
```

.env

```
APP_PORT=8000
REDIS_HOST=redis
REDIS_PORT=6379
```

### Step 3: Run pethost deploy

```
$ cd counter
$ pethost deploy
Name it [counter]:              it will live at https://counter.sam.pethost.app
✓ Packed 6 files, 1 KB   (took .env from this folder)
✓ Uploaded straight to your machine
✓ Built and started in 28 s
✓ web listens on 5000 → https://counter.sam.pethost.app
  It answers 200.

https://counter.sam.pethost.app

Wrote .pethost (commit it). Next:   pethost logs -f   ·   pethost open
```

It asks the project's name, the first word of its address, which never changes; Enter takes the folder's. The machine built `web`, pulled `redis:alpine` and waited until both ran. Commit `.pethost`, which names the project.

### Step 4: Open the address

```
$ curl https://counter.sam.pethost.app/
Hello from Docker! I have been seen 3 time(s).
```

The count starts above 1 because your machine and `pethost deploy` asked the new address first, to see that it answers.

**Turn the debugger off before you share the address.** The quickstart starts `flask run --debug`, so its logs say “Debugger is active!”. Flask's [documentation](https://flask.palletsprojects.com/en/stable/deploying/) says not to use its development server in production: drop `--debug`, or run Gunicorn.

## Deploy from the panel: an archive or a GitHub repository

### Step 1: Click + New

New project offers **GitHub repository**, **Upload an archive**, **Write compose.yaml** and **From a template**, with the databases under them.

**New project, its first step.** The four ways to start a project, and the one-click databases under them.

![The New project dialog of the Pethost panel: GitHub repository, Upload an archive, Write compose.yaml and From a template, then Postgres 18, MariaDB 11 and Valkey 9 under Database.](https://pethost.dev/shots/docs-new-project.webp)

### Step 2: Drop an archive, or pick a repository

An archive is a .zip, .tar or .tar.gz of the folder, and the panel says what will run: “Runs its compose.yaml.” A repository is one of your own GitHub account's, with its **Branch** and **Directory in the repository**; **Install on more repositories** lets Pethost's GitHub App read it. Then **Next**.

### Step 3: Check the variables, then Create project

**Environment variables** is the project's .env: an archive's own fills it, and for a repository, which seldom holds one, you type its lines. **Create project** deploys it.

**The project, deployed.** counter's two services, `redis` healthy and `web` running, its address, and its requests of the last day.

![The counter project's Overview tab in the Pethost panel: requests, errors, response time and uptime, the services redis and web with their memory, the source, and the address counter.sam.pethost.app.](https://pethost.dev/shots/docs-compose-project.webp)

## Deploy it with your AI agent

### Step 1: Install the Pethost plugin

Tell your agent: “Install the Pethost plugin from https://github.com/pethost-dev/plugin for yourself.” You approve it in your browser. The [plugin's page](https://github.com/pethost-dev/plugin) has the command for Claude Code, Codex, Cursor and others.

### Step 2: Ask for the deploy

Claude Code

**You say:** Deploy this Compose app and send me the link.

- GetMachine sam.pethost.app, no project counter yet
- CreateTransfer counter.tgz uploaded
- CreateProject counter: succeeded

**The agent answers:** It's live at https://counter.sam.pethost.app. Your compose.yaml ran as written.

The agent sends the folder as it is, and fixes what a refusal names: [how an agent deploys a project](https://pethost.dev/blog/hosting-for-ai-coding-agents/).

## Which service gets the public address?

Pethost keeps `ports:` as you wrote it, and asks each published TCP port what it is:

- **A port that answers HTTP** gets an HTTPS address and is not opened on the machine: `"${APP_PORT}:5000"`, 8000:5000 here, became https://counter.sam.pethost.app, sent to port 5000 of `web`. Where several services answer HTTP, each gets `<project>-<service>.sam.pethost.app`.
- **Any other published port** opens on the machine as written, to anyone. `"6379:6379"` under `redis` would put Redis on the internet: leave it out, or bind it to 127.0.0.1, which is never opened. `pethost tunnel redis` reaches it either way.
- **A service with no `ports:`** is private: the project's services reach it at `redis:6379`.

To send a path to another service, say an `api` beside `web`, write routes under `x-pethost`. `port` is the port in the container, 5000, not 8000:

compose.yaml

```
x-pethost:
  routes:
    - {host: counter.sam.pethost.app, service: web, port: 5000}
    - {host: counter.sam.pethost.app, path: /api, service: api, port: 8000, strip_path: true}
```

The file's `x-pethost` counts at the first deploy; later ones keep the project's, so change routes on the **Domains** tab or with `pethost domain add hits.sam.pethost.app`. A domain you own is one DNS record: [give an app a custom domain](https://pethost.dev/docs/custom-domain/).

## Where does the data live?

In named volumes. Whatever else a container writes is lost when it is made anew, by a deploy that changes its service or by `pethost restart --pull`. The quickstart's step 5 mounts a volume. That file, with step 3's healthcheck, ran as written:

compose.yaml, step 5: the volume's lines

```
redis:
    image: redis:alpine
    volumes:
      - redis-data:/data

volumes:
  redis-data:
```

The deploy that added it started the count over, as Redis moved to the empty volume. Since then it goes on, in a new container too:

```
$ pethost restart --pull redis
counter (.pethost)
✓ redis runs in a new container, on the newest image of its tag, in 6 s.
$ curl https://counter.sam.pethost.app/
Hello from Pethost! I have been seen 4 time(s).
```

Every deploy and rollback keeps the volume, and nightly backups hold it, off the machine, for 14 days: `pethost backup restore <id> --volume redis-data` puts it back. A bind mount of a folder without `:ro`, `./data:/data`, gets the project's volume `data`, not your folder's files.

## Environment variables and secrets

The folder's `.env` goes with the first deploy only; after that the project keeps its own, and a deploy does not replace it. It fills each `${…}` in compose.yaml, and a service gets a variable only where compose.yaml passes it:

```
$ pethost env
counter (.pethost)
APP_PORT     no service reads it
REDIS_HOST   web
REDIS_PORT   web
Their values:  pethost env --show

$ pethost env set SECRET_KEY
counter (.pethost)
SECRET_KEY: ••••••••
✓ /.env has SECRET_KEY now. Deployed in 1 s.
```

A bare name asks for the value without showing it. `web` reads `SECRET_KEY` once compose.yaml passes it, as `- SECRET_KEY=${SECRET_KEY}` under `environment:`, or `env_file: .env` for every variable. After `pethost deploy`:

```
$ pethost env
counter (.pethost)
APP_PORT     no service reads it
REDIS_HOST   web
REDIS_PORT   web
SECRET_KEY   web
Their values:  pethost env --show
```

`pethost env push` sends the folder's .env again, after naming what would go. A literal `$` in compose.yaml is written `$$`. An email provider's key goes in the same way: [send email from your app](https://pethost.dev/docs/send-email/).

## Deploy a new version, and roll back

Change the code and run the same command. The build's layers stay on the machine, so it took 3 seconds:

```
$ pethost deploy
counter (.pethost)
✓ Packed 7 files · uploaded · built and started in 3 s
  · kept the project's .env in place of the version's own: after the first deploy the project's is kept
  · kept the project's x-pethost
  It answers 200.
https://counter.sam.pethost.app
To send this folder's .env:  pethost env push
```

Your machine keeps the files of the three versions before the current one:

```
$ pethost rollback
counter (.pethost)
✓ Back on the version deployed today at 20:38, in 3 s.
  /.env, the routes and the volumes' data stay as they are now.
  This folder is as it was: the next pethost deploy puts its code online again.
```

`pethost rollback --list` shows the versions. To deploy every push instead, connect the repository once; a rollback then goes to a commit and stops following the branch until you run `pethost github` again:

```
pethost github sam/counter          # from now on, every push deploys itself
```

## Run a database next to your app

A database service in compose.yaml, as `redis` here, runs as written, its data in a volume backed up with the project. Or make a one-click database, a project of its own: **+ New** lists Postgres, MariaDB and Valkey, which Redis's clients work with. It comes with a data editor in the browser and backups of its own.

Linked at the app's create ([CreateDatabase](https://pethost.dev/docs/api/#CreateDatabase), then `link_databases`), the machine writes `DATABASE_URL` and its parts into the app's `.env`, so no password is copied. The app reaches it at the database's project id: [Postgres in one click](https://pethost.dev/postgres/).

## What compose.yaml features are not supported?

Few. The quickstart's last version, with `include: ./infra.yaml` and `develop: watch`, ran as written.

**Compose features on Pethost**

| In compose.yaml | On Pethost | Write instead |
| --- | --- | --- |
| `.:/code`, the project mounted writable | Refused | `.:/code:ro` |
| `privileged`, `cap_add`, `devices`, host networking, the Docker socket | Refused: they could reach the machine | Nothing |
| More than one replica | Refused | One container a service |
| A volume's `name`, `driver` or `external` | Refused | The volume's key alone |
| A volume with no name, `- /data` | Refused: no backup holds it | `data:/data` |
| `include` or `extends` from a URL, a git URL as build context | Refused | The file in the folder |
| `develop: watch` | Accepted, unused online | `pethost deploy` |
| compose.override.yaml | Left out | Its lines in compose.yaml |

Where your compose.yaml is made for a laptop, keep it and write `pethost.compose.yaml` beside it: Pethost takes that file first. Every rule is in the [API reference](https://pethost.dev/docs/api/#CreateProject).

## When a deploy fails

**The file is refused.** Each line with a cross names the place and what to write, and nothing changes on the machine:

```
$ pethost deploy
counter (.pethost)
  · kept the project's .env in place of the version's own: after the first deploy the project's is kept
  · kept the project's x-pethost
✗ services.web.volumes[0]: the bind mount at /code is of the project's whole directory and writable, but a deploy replaces the project's files: add :ro, and keep data in a folder of its own, e.g. ./data:/data
  Nothing was deployed. Change that in the folder, then run pethost deploy again.
```

- **A service exits or turns unhealthy.** The deploy fails, its log ending with that service's last 20 lines: `pethost logs --deploy`. `pethost why` shows what is wrong.
- **The address answers 502.** Nothing listens on the routed port, or only at 127.0.0.1, which the proxy cannot reach. That is why the quickstart's Dockerfile sets `FLASK_RUN_HOST=0.0.0.0`.
- **A port is taken.** A published port that is not HTTP and that another project holds fails the deploy: change the number left of the colon.
- **An image declares a VOLUME nothing mounts.** Mount a named volume there.

## Questions

### Can I deploy a docker-compose.yml file?

Yes. Pethost takes compose.yaml, compose.yml, docker-compose.yml or docker-compose.yaml at the folder's root, and pethost.compose.yaml before any of them. Override files are left out.

### Do I need to change my compose file for Pethost?

Usually not: Docker's Compose quickstart ran unchanged. A file Pethost will not run is refused before anything starts, saying what to write instead.

### How do I add a custom domain to a Compose app?

Run `pethost domain add www.example.com` in the project's folder. It prints one DNS record to make, and the certificate comes by itself once the name resolves: [the custom domain guide](https://pethost.dev/docs/custom-domain/).

### How much does Docker Compose hosting cost on Pethost?

Starter is €9.99 a month for 2 vCPU, 3 GB of memory and 30 GB of SSD, for as many projects as fit; Grow is €17.99 for twice that. Nothing is metered. There is no free tier, and prices are without VAT: [the plans](https://pethost.dev/#pricing).

The screens are deploys of Docker's Compose quickstart, read on 29 October 2026, on a test machine of ours. Other hosts that take a Compose file: [managed Docker Compose hosting in 2026](https://pethost.dev/blog/managed-docker-compose-hosting/).

[NextAdd a custom domain to your app](https://pethost.dev/docs/custom-domain/)

## Run your apps on Pethost

Deploy apps, bots and databases from your agent, GitHub or the browser, as many as fit, at one flat price. Nothing metered, nothing billed on top.

[Host all your projects for €9.99/mo](https://console.pethost.dev/)

Pethost · Stephan Gomer · NIP 1133096755

Pethost

- [Templates](https://pethost.dev/templates/)
- [About](https://pethost.dev/about/)
- [Docs](https://pethost.dev/docs/)
- [Blog](https://pethost.dev/blog/)
- [API](https://pethost.dev/docs/api/)
- [CLI](https://pethost.dev/docs/cli/)
- [Plugin](https://github.com/pethost-dev/plugin)
- [Terms](https://pethost.dev/terms/)
- [Privacy](https://pethost.dev/privacy/)
- [Refunds](https://pethost.dev/refunds/)
- [support@pethost.dev](mailto:support@pethost.dev)

Databases

- [Postgres](https://pethost.dev/postgres/)

Comparisons

- [Coolify vs Dokploy vs CapRover vs Dokku](https://pethost.dev/blog/coolify-vs-dokploy-vs-caprover-vs-dokku/)
- [Coolify Cloud vs Dokploy Cloud](https://pethost.dev/blog/coolify-cloud-vs-dokploy-cloud/)
- [Railway vs Render vs Fly.io pricing](https://pethost.dev/blog/railway-vs-render-vs-fly-io-pricing/)
- [Managed Docker Compose hosting](https://pethost.dev/blog/managed-docker-compose-hosting/)

Alternatives

- [Railway alternatives](https://pethost.dev/blog/railway-alternatives/)
- [Heroku alternatives](https://pethost.dev/blog/heroku-alternatives/)
- [Dokploy alternatives](https://pethost.dev/blog/dokploy-alternatives/)
- [Coolify alternatives](https://pethost.dev/blog/coolify-alternatives/)
- [Render alternatives](https://pethost.dev/blog/render-alternatives/)
- [Sliplane alternatives](https://pethost.dev/blog/sliplane-alternatives/)
