# Moving from Docker Compose

Move a project you run with Docker Compose (on a VPS, a home server or your laptop) to Deplane in one command: `deplane import compose` reads your `docker-compose.yml`, shows what each service becomes and what cannot move, then creates the apps, workers, managed databases, Redis, disks and secrets, and deploys. The dashboard does the same for a one-app file under **New app**, **Import from Docker Compose**.

The command and its flags are in the [CLI reference](cli.md#deplane-import-compose), and every error has a fix in [Troubleshooting and error codes](errors.md#compose_import_refused).

## Check first

Run it in the folder with your compose file:

```sh
$ deplane import compose --check
Docker Compose file /home/me/shop/compose.yaml → Deplane, prefix shop

Services
  ✓ web → app shop built from /home/me/shop (Railpack, or its Dockerfile), port 3000, 1 small instance(s), public at https://shop.deplane.app. deplane.toml is written there.
  ✓ worker → worker worker of shop (same build .), 1 small instance(s): `node worker.js`.

Data
  ✓ db (postgres:16) → a new managed Postgres shop-db, small, attached to shop as DATABASE_URL.
  ⚠ db: its data is not copied; the new database starts empty. To copy it, run `deplane db import --name shop-db-copy` ...
  ✓ cache (redis:7) → a new managed Redis shop-redis, Valkey 8, 256 MB, attached to shop as REDIS_URL.

Variables
  ⚠ shop: DB_PASSWORD is set to the managed Postgres's password (it pointed at db).
  ✓ shop: 3 copied as secrets (values are never shown): APP_KEY, NODE_ENV, SESSION_SECRET.

Disks
  ✓ web: the volume uploads → a 5 GB disk for shop at /data/uploads, backed up every night. ...

Plan
  ✓ Fits your Pro plan: after the import the team uses 3 of 30 instances and 1.8 GB of 16 GB memory.

Nothing was changed (--check). Run the command again without --check to import.
```

Each line is ✓ (moves as it is), ⚠ (moves, read it) or ✗ (stops the import until you fix it). Variable names are listed; their values never are, in the report, `--json` or the dashboard. When you are logged in, the report also checks your plan: a ✗ line says what it lacks. `--check` creates nothing. Then import:

```sh
$ deplane import compose --yes
```

`-f FILE` picks the file (default: `compose.yaml`, `compose.yml`, `docker-compose.yaml` or `docker-compose.yml`, in that order). `--prefix NAME` names the apps: the main app is `NAME`, the others `NAME-SERVICE` (default: the file's `name:`, else its folder's name).

## What moves, and how

| In your file | On Deplane |
|---|---|
| A service with `ports:` and an `image:` | An app [deployed from that image](quickstart.md#deploy-a-docker-image), on the container's port. |
| A service with `ports:` and a `build:` | An app built from that folder, as `deplane deploy` builds it (CLI only). |
| `postgres`, `postgis/postgis`, `bitnami/postgresql` | A managed [Postgres](databases.md), attached as `DATABASE_URL`. |
| `mysql`, `mariadb`, `bitnami/mysql`, `bitnami/mariadb` | A managed [MySQL](mysql.md) 8.4, attached as `MYSQL_URL`. |
| `redis`, `valkey/valkey`, `bitnami/redis` | A managed [Redis](redis.md), attached as `REDIS_URL`. |
| `clickhouse/clickhouse-server` | A managed [ClickHouse](clickhouse.md) database, attached as `CLICKHOUSE_URL`. |
| A service without `ports:` that shares an app's image or build | A [worker](workers.md) of that app, with its `command`. |
| Any other service without `ports:` | An app of its own with the [private network](private-networking.md) on, reached at `http://APP`. |
| A named volume | A [disk](disks.md) of 5 GB at the same path. |
| `environment:` and `env_file:` | Secrets on the app. |
| An HTTP `healthcheck` (`curl` or `wget` of `http://localhost/...`) | The app's health check path. |
| `deploy.replicas`, `deploy.resources.limits.memory` or `mem_limit` | Instances, and the size: up to 512 MB small, up to 1 GB medium, else large. |

**Databases are attached to the services that use them**: a service that lists the database in `depends_on` or `links`, or has a variable with its host. Variables that point at the database are filled in from the managed one: `DATABASE_URL` and the other attach variables are set by attaching, `postgres://user:pass@db:5432/app` becomes the managed URL, `DB_HOST: db` the managed host, and a variable whose value is the old database's user, password, database name or port (and whose name says so, such as `DB_PASSWORD` or `WORDPRESS_DB_USER`) gets the managed one. Deplane fills these in on its servers: the managed passwords are never sent to your computer. It also remembers where each came from, so when the app moves to another database (`deplane db switch`, after a restore or a `db import`) or gets another one attached, they follow it in the same release. Set one by hand and it stops following. A form Deplane cannot fill in (another URL scheme such as `postgresql+asyncpg://`, or a JDBC URL) is left out with a ⚠: read `DATABASE_URL` in your app instead.

**Your data is not copied.** The new databases start empty. Copy Postgres with [`deplane db import`](import-postgres.md) while the old database still runs, then `deplane db switch`. Load MySQL from a dump through `deplane run`. Copy a volume's files onto the app's disk after the import.

**One app per public service.** A service that publishes several ports needs one chosen, with a Compose extension field Deplane reads:

```yaml
services:
  wordpress:
    image: bitnami/wordpress:6
    ports: ["80:8080", "443:8443"]
    x-deplane:
      port: 8080
```

**Variables.** `${VAR}`, `${VAR:-default}` and the other Compose forms are filled in from your environment, then from `.env` beside the file. A variable with no value is left empty, with a ⚠ naming it; `${VAR:?message}` without one stops the import. The report names every variable it took from your environment, every `environment:` entry it filled from your shell, and every `env_file` or build folder outside the compose file's folder, so a file you did not write cannot quietly send your local secrets to an app. Commands are shown with values from your environment, URL passwords and the service's variable values replaced by `***`. A value over 32 KB is not copied, and an app's variables must add up to less than 192 KB. `PORT` and `DEPLANE_*` are set by Deplane. Build `args:` become [build-time secrets](secrets.md).

## What cannot move

These stop the import with a ✗ line and what to do:

- `privileged: true`, `cap_add`, `devices`, `gpus`, `runtime`, `device_cgroup_rules`, `volumes_from`, `network_mode: host` (or any mode but `bridge`), and `pid`, `ipc`, `cgroup`, `uts` or `userns_mode` set to `host` or to another container: Deplane runs each app in its own microVM, without extra privileges.
- An image reference Deplane cannot deploy: a private registry or a private address, or one that is not `[registry/]name[:tag]` in lowercase.
- A host folder mounted by absolute path (`/srv/data:/data`) and the Docker socket. Put files the app only reads into its image; for files it writes, use a named volume (a disk) and copy them there after the import.
- A port below 1024: Deplane runs apps as user 10001, never root. Use an image made for that (for example `nginxinc/nginx-unprivileged` on 8080, or `bitnami/wordpress`), or Deplane's [WordPress template](templates.md).
- A second named volume on one app (an app has one disk), two databases of one kind (an app has one `DATABASE_URL`), and services Deplane has no managed version of that do not speak HTTP (RabbitMQ, MongoDB, Memcached, Kafka, NATS and others).
- `include:` and `extends:`: run `docker compose config > resolved.yml` and import `resolved.yml`.
- A build from a non-default Dockerfile (`dockerfile: Dockerfile.prod`): Deplane builds the folder's own `Dockerfile`.
- Anything your plan cannot hold: workers and the private network need a plan whose apps never sleep (Hobby and larger), and the apps, instances, memory, databases, Redis, MySQL, ClickHouse and disk space must fit. See [Limits](limits.md#plans).

These move with a ⚠ line: a relative bind mount (`./src:/app`, not mounted: build the files into the image), an anonymous volume (not kept), `command:` or `entrypoint:` on an app (Deplane runs the image's own command, or the folder's Dockerfile or Procfile), `user:` (apps run as user 10001), a healthcheck that is not HTTP (a TCP check instead), MariaDB (Deplane runs MySQL 8.4), and Compose `secrets:` and `configs:` (set them as variables). `restart`, `container_name`, `networks`, `labels` and `depends_on` order are not needed. Services with `profiles:` are not imported, as `docker compose up` does not start them.

## In the dashboard

Under **New app**, **Import from Docker Compose**, paste the file or upload it, press **Check** for the same report, then **Import**. The import runs on Deplane's servers as a [template deploy](templates.md): the steps show as they run, and a failed step has **Retry** and **Delete all of it**. The dashboard imports one app from a public image, with its databases, Redis, MySQL, ClickHouse, disk and workers. A file with services built from a folder, several apps, `env_file` or variables from your shell needs the CLI. Values from the file are stored sealed until the import sets them as the app's secrets.

Only a person at their own login checks or imports: CI tokens and [AI tools](mcp.md) cannot, since a compose file usually holds passwords (the CLI plans on your own computer). A team can check or import 30 files in 10 minutes in the dashboard ([too_many_compose_checks](errors.md#too_many_compose_checks)), and a file that would take more than 5 seconds to plan is refused ([compose_too_complex](errors.md#compose_too_complex)).

## If a step fails

The CLI stops at the first step that fails, lists what it created, the commands to finish by hand, and the commands to delete it all (`deplane apps destroy`, `deplane db destroy` and the like). Nothing is created before the report has no ✗ line.

## Limits

| Limit | Value |
|---|---|
| File size | 256 KB; 512 KB of values with anchors expanded |
| Services | 64 |
| YAML | One document; at most 20000 values with anchors expanded, 32 levels deep, 200 aliases ([compose_too_complex](errors.md#compose_too_complex)) |
| Apps in the dashboard | 1 (from an image); the CLI has no such limit |
| Databases of each kind | 1 per file |
| Disks | 1 per app, 5 GB |
| Workers | 10 per app, 20 instances each |
