deplaneDocs Dashboard

CLI reference

Every deplane command with its flags, a human example, and its --json output.

Run deplane help for a short list. Commands that act on an app use the app in ./deplane.toml, or the one you pass with --app NAME.

Global flags

These flags work with every command, anywhere on the command line.

FlagWhat it does
--jsonPrint exactly one JSON document on stdout. Streams print one JSON object per line. Progress messages still go to stderr.
--yesAnswer yes to every confirmation.
--debugLog each HTTP request and response to stderr. Tokens, cookies, Authorization headers, and secret values are replaced with [REDACTED].
--app NAMEThe app to act on. Default: app in ./deplane.toml.
--helpShow the command list.

Without a terminal, the CLI never asks a question. A command that needs confirmation fails with confirmation_required and tells you to pass --yes.

Flags that take a value accept --flag VALUE or --flag=VALUE.

Environment variables

VariableWhat it does
DEPLANE_TOKENUse this token instead of the stored login.
DEPLANE_API_URLThe API to use. Default https://api.deplane.io.
DEPLANE_ID_TOKENIn a GitLab CI job: the ID token (id_tokens:, audience your API URL). Used when DEPLANE_TOKEN is not set, exchanged for a 15-minute pipeline token, never printed.
BITBUCKET_STEP_OIDC_TOKENIn a Bitbucket Pipelines step with oidc: true: used like DEPLANE_ID_TOKEN.
DEPLANE_TEAMIn a pipeline job: the team's slug, when one repository is trusted by apps of several teams.
HEROKU_API_KEYThe Heroku token for deplane import heroku when none is piped in. Sent only to Heroku.
RENDER_API_KEYThe Render API key for deplane import render when none is piped in. Sent only to Render.
RAILWAY_TOKENThe Railway token for deplane import railway when none is piped in. Sent only to Railway.
CLOUDFLARE_API_TOKENThe Cloudflare API token for deplane domains switch when none is piped in. Sent only to Cloudflare.
FLY_API_TOKENThe Fly.io token for deplane import fly when none is piped in. Sent only to Fly.io.
VERCEL_TOKENThe Vercel token for deplane import vercel when none is piped in. Sent only to Vercel. VERCEL_TEAM_ID names the project's team when the argument does not.
NETLIFY_AUTH_TOKENThe Netlify token for deplane import netlify when none is piped in. Sent only to Netlify.
CPANEL_API_TOKENThe cPanel API token for deplane import cpanel when none is piped in. Sent only to cPanel.

Exit codes

The exit codes are stable. Scripts and agents can rely on them.

CodeMeaning
0Success.
1User error: a bad flag, a bad value, a missing app, a cancelled confirmation, a suspended app (app_suspended), and any other refusal.
2Authentication or permission error: not logged in, login expired, a fresh passkey confirmation is needed, or your role or token may not do this.
3The build failed.
4The deploy failed: the new release did not become healthy.
5Platform error: the API could not be reached, or something failed on our side.
6Quota exceeded: every error code that starts with limit_ exits with 6.

Errors

Every error says what happened and how to fix it. Without --json it goes to stderr:

$ deplane scale 9
Error: This needs 9 instances in total and your plan allows 3.
Fix: Use fewer instances, scale down another app, or email support@deplane.io to raise the limit.

With --json the error is the one JSON document on stdout:

{
  "error": {
    "code": "limit_instances",
    "message": "This needs 9 instances in total and your plan allows 3.",
    "fix": "Use fewer instances, scale down another app, or email support@deplane.io to raise the limit.",
    "exit_code": 6
  }
}

Every code is listed in Troubleshooting and error codes.

Account

deplane login

Log in through your browser with your passkey. The CLI shows a code, opens https://app.deplane.io/device, and waits until you approve. The token is stored in your OS keychain, or in a file only you can read if there is no keychain. It lasts 30 days. See Install for the full flow.

No flags besides the global ones. It refuses to run while DEPLANE_TOKEN is set.

$ deplane login

To log in, open https://app.deplane.io/device and type this code:

    WDJB-MJHT

Only approve it if you just ran this command yourself. The code expires in 10 minutes.

Logged in as you@example.com (organization you-example).
{
  "email": "you@example.com",
  "logged_in": true,
  "org": "you-example",
  "stored_in": "keychain"
}

stored_in is keychain or file.

deplane logout

Revoke this computer's token and remove it.

$ deplane logout
Logged out. The token was revoked and removed from this computer.
{
  "logged_out": true,
  "was_logged_in": true
}

deplane whoami

Show who you are logged in as, your organization, and its plan limits.

$ deplane whoami
Logged in as you@example.com
Organization: You (you-example)
{
  "entitlements": {
    "memory_mb": 1024,
    "apps": 1,
    "instances": 2,
    "sizes": ["small"],
    "build_minutes_month": 500,
    "concurrent_builds": 1,
    "log_retention_days": 7,
    "log_streams": 2,
    "assistant_enabled": true,
    "assistant_requests_month": 100,
    "backup_retention_days": 1,
    "disk_gb": 5,
    "redis": 1,
    "mysql": 0,
    "clickhouse": 0,
    "databases": 1,
    "members": 1,
    "custom_domains": 3,
    "previews": false,
    "always_on": false
  },
  "org": {
    "id": "org_2b7k9m4x1q8w5e3r6t0y",
    "name": "You",
    "plan_id": "pln_free",
    "plan_name": "Free",
    "slug": "you-example"
  },
  "user": {
    "email": "you@example.com",
    "id": "usr_8c3v6b1n9m4k2j7h5g0f",
    "is_admin": false
  }
}

deplane version

Print the CLI version.

$ deplane version
deplane 0.1.0
{
  "version": "0.1.0"
}

deplane doctor

Check that the API answers, that you are logged in, whether this folder is a project Deplane can build, and whether deplane.toml is valid. Each failed check has a fix. The exit code is the code of the first failed check, or 0.

$ deplane doctor
ok   api      The Deplane API at https://api.deplane.io answers.
FAIL login    You are not logged in, or the login expired.
              Fix: Run `deplane login`, or set DEPLANE_TOKEN.
ok   project  Detected Flask 3, Python 3.12, port 8000.
ok   config   deplane.toml is valid (app my-api).
{
  "ok": false,
  "checks": [
    {"name": "api", "ok": true, "detail": "The Deplane API at https://api.deplane.io answers."},
    {"name": "login", "ok": false, "detail": "You are not logged in, or the login expired.", "fix": "Run `deplane login`, or set DEPLANE_TOKEN."},
    {"name": "project", "ok": true, "detail": "Detected Flask 3, Python 3.12, port 8000."},
    {"name": "config", "ok": true, "detail": "deplane.toml is valid (app my-api)."}
  ]
}

Tokens

deplane tokens list

List your API tokens. kind is cli for logins, ci for tokens made with tokens create, and agent for tokens from agent login.

$ deplane tokens list
ID                         NAME            KIND  EXPIRES     LAST USED
tok_5h2j8k1l4z7x9c3v6b0n   macbook         cli   2026-11-12  2026-10-13
tok_1q9w3e7r5t2y8u4i6o0p   github-actions  ci    2027-01-11  never
{
  "tokens": [
    {
      "id": "tok_1q9w3e7r5t2y8u4i6o0p",
      "name": "github-actions",
      "kind": "ci",
      "expires_at": "2027-01-11T09:00:00Z",
      "last_used_at": null,
      "created_at": "2026-10-13T09:00:00Z"
    }
  ]
}

deplane tokens create

Create a CI token for CI, agents, and scripts: deplane tokens create NAME. The token is shown once. Store it as DEPLANE_TOKEN in your CI secrets.

Creating a token needs a fresh confirmation with your passkey from the last 5 minutes. In a terminal, the CLI opens your browser for it and then continues. CI tokens cannot create tokens.

FlagWhat it does
--name NAMEThe token's name, instead of the positional argument.
--expires-in-days NLifetime from 1 to 365 days. Default 90.
$ deplane tokens create github-actions --expires-in-days 90
Token "github-actions" (tok_1q9w3e7r5t2y8u4i6o0p) created; it expires on 2027-01-11. It is shown only once: store it in your CI secrets as DEPLANE_TOKEN.
dpl_4Fh7Kq2Lm9Np3Rs6Tv8Wx1Yz5Ab0Cd

The message goes to stderr and only the token goes to stdout, so deplane tokens create ci > token.txt saves just the token.

{
  "token": "dpl_4Fh7Kq2Lm9Np3Rs6Tv8Wx1Yz5Ab0Cd",
  "id": "tok_1q9w3e7r5t2y8u4i6o0p",
  "name": "github-actions",
  "kind": "ci",
  "expires_at": "2027-01-11T09:00:00Z"
}

deplane tokens revoke

Revoke a token at once: deplane tokens revoke ID. Asks for confirmation; pass --yes to skip it.

$ deplane tokens revoke tok_1q9w3e7r5t2y8u4i6o0p --yes
Revoked tok_1q9w3e7r5t2y8u4i6o0p.
{
  "revoked": "tok_1q9w3e7r5t2y8u4i6o0p"
}

deplane agent login

Get a scoped token for an AI agent: deplane agent login --role ROLE. You approve it in your browser, which shows the role, the apps and how long it lasts. The token is printed once, for the agent's DEPLANE_TOKEN, and never saved. Roles and limits: Agent tokens.

FlagWhat it does
--role ROLEviewer, deployer or operator. Required.
--app A,BLimit the token to these apps. Default: all the team's apps.
--name NAMEThe agent's name, shown on the approval page, in the email and in the activity log. Default agent.
--ttl DLifetime: 12h, 7d, up to 30d. Default 7 days.
$ deplane agent login --role deployer --app web --name claude-code
Approved: a deployer token for dev@example.com (organization acme). It is shown only once; use it as DEPLANE_TOKEN. Revoke it with `deplane tokens revoke`.
dpl_4Fh7Kq2Lm9Np3Rs6Tv8Wx1Yz5Ab0Cd
{
  "token": "dpl_4Fh7Kq2Lm9Np3Rs6Tv8Wx1Yz5Ab0Cd",
  "role": "deployer",
  "email": "dev@example.com",
  "org": "acme"
}

Apps and deploys

deplane launch

Detect how to build this folder, create the app, write deplane.toml, build, deploy, and print the URL. See the Quickstart for what it detects.

FlagWhat it does
--name NAMEThe new app's name. Default: the folder name, with a random suffix if that name is taken.
--app NAMEDeploy this folder to an app you already have (the same as deplane deploy --app NAME).
--repo OWNER/REPO[@REF]Deploy a public GitHub repository that has a deplane.template.json, instead of the folder. See below.
--image REFDeploy a public Docker image instead of the folder. See below.
--port PORTWith --image: the port the image listens on. It goes with the build and becomes the app's port when that build is released, so an image that is refused or fails to build leaves the port as it was; once released, the port stays even if that release fails its health check or is rolled back. Default 8080.

deplane launch --image REF [--name APP] [--port PORT] creates the app from a public Docker image, with no source code. REF is [registry/]name[:tag][@sha256:DIGEST], for example nginxinc/nginx-unprivileged:1.27; the app's name defaults to the image's name. Deplane builds the image into a release like any deploy, so the release command runs too. Only public images work, and Deplane runs them as user 10001, not root; see Deploy a Docker image. No deplane.toml is written, because an image has no folder: pass --app NAME to later commands. To update the app to a new image, run deplane deploy --image REF --app NAME. --image cannot be combined with a folder, a path or --template; it fails with image_with_source. A bad reference fails with invalid_image, and a private registry with private_registry_unsupported.

$ deplane launch --image nginxinc/nginx-unprivileged:1.27 --port 8080
Created app nginx-unprivileged.
Build bld_7c3x9k2m4q8w1e5r6t0y queued for nginxinc/nginx-unprivileged:1.27.
...
Deployed nginx-unprivileged v1: https://nginx-unprivileged.deplane.app

deplane launch --template NAME [--name APP] deploys a template instead of the folder: Deplane creates the app (and a database and disk if the template needs them), sets its secrets, builds it from Deplane's own copy of the template and rolls it out, while the command shows each step. It ends with the app's URL and what to do next, for example "Open the URL and create the first admin account". If a step fails, everything made so far is kept and the command exits with code 4 and tells you how to retry or delete it. --no-wait returns at once. See Templates.

deplane launch --repo OWNER/REPO[@REF] [--name APP] deploys a public GitHub repository that carries a deplane.template.json at its root, pinned to a commit: the command first prints what the file asks for (the app, its database, Redis, MySQL, ClickHouse database or disk, the variables it sets, its warning) and a reminder that Deplane has not reviewed the repository, then deploys and follows the steps exactly like --template. REF is a branch, a tag or a commit (default: the default branch); OWNER/REPO may also be written as github.com/owner/repo or the repository's URL. It cannot be combined with --template or --image. See Templates.

Run again in the same folder, launch deploys that folder's app again. It never deploys a folder over a different existing app: --name with the name of an app you already have stops with an error, and a folder whose name matches an existing app gets a new app with a suffix.

$ deplane launch
Detected Next.js 15, Node 22, port 3000
Created app my-shop.
Wrote deplane.toml.
Uploading 214 files (1.3 MB)...
Build bld_4m8q2k7x9c1v5b3n6z0a queued.
Building...
...
Built 4f1c2a9e8b7d. Rolling out release v1...
Running: 1 of 1 instances ready.
Deployed my-shop v1: https://my-shop.deplane.app

With --json, progress and the build log still go to stderr, and stdout gets one document:

{
  "app": "my-shop",
  "build": "bld_4m8q2k7x9c1v5b3n6z0a",
  "created": true,
  "image_digest": "sha256:4f1c2a9e8b7d...",
  "release": 1,
  "status": "running",
  "url": "https://my-shop.deplane.app"
}

A failed build exits with 3. A release that does not become healthy exits with 4.

deplane deploy

Build and deploy this folder's app again. It applies deplane.toml first. It does not create apps: without a deplane.toml and without --app, it fails with no_app.

FlagWhat it does
--app NAMEDeploy this folder to this app.
--image REFDeploy a public Docker image to the app instead of the folder, for example deplane deploy --image ghcr.io/acme/web:2.4 --app web. The image replaces the app's current release when it is healthy.
--port PORTWith --image: the port the image listens on; it becomes the app's port when the build is released. With a deplane.toml, its other [service] settings (health check, release command) are still applied before the build.

deplane.toml's release_command is sent with the other [service] settings, and a deploy that has a release command prints "Running the release command..." while it runs, waits up to 15 minutes more, and on failure prints the command's output and exits with code 4. --image takes no folder or path: see deplane launch for the image rules.

$ deplane deploy
Detected Next.js 15, Node 22, port 3000
Uploading 214 files (1.3 MB)...
Build bld_9t2w6r1y8u3i5o7p4a0s queued.
Building...
...
Built 0b9e7d61a2c3. Rolling out release v2...
Running: 1 of 1 instances ready.
Deployed my-shop v2: https://my-shop.deplane.app
{
  "app": "my-shop",
  "build": "bld_9t2w6r1y8u3i5o7p4a0s",
  "created": false,
  "image_digest": "sha256:0b9e7d61a2c3...",
  "release": 2,
  "status": "running",
  "url": "https://my-shop.deplane.app"
}

deplane redeploy

Build the app's last source again (an upload or a GitHub commit) and release it, for example after changing a build variable with deplane secrets set --build. It counts toward the 10 deploys per hour.

$ deplane redeploy
Rebuilding shop from its last source. Build bld_… queued.
Deployed shop v8: https://shop.deplane.app

deplane apps

List your organization's apps. An autoscaled app shows its range in INSTANCES (for example 1-5), and an app that is asleep shows sleeping in STATUS.

$ deplane apps
NAME     STATUS  RELEASE  INSTANCES  SIZE   URL
my-shop  active  v2       1          small  https://my-shop.deplane.app
my-api   active  v5       2          small  https://my-api.deplane.app
{
  "apps": [
    {
      "id": "app_7k2m9x4q1w8e5r3t6y0u",
      "name": "my-shop",
      "region": "fra",
      "status": "active",
      "url": "https://my-shop.deplane.app",
      "current_release": 2,
      "instances": 1,
      "size": "small",
      "port": 3000,
      "health_check": {"type": "tcp"},
      "created_at": "2026-10-13T09:12:00Z"
    }
  ]
}

status is active or suspended. health_check is {"type": "tcp"} or {"type": "http", "path": "/healthz"}.

deplane apps destroy

Destroy any app by name: deplane apps destroy NAME. Its instances, releases, and secrets are deleted, and its disk if it has one (the question says so), and the name becomes available to others. Asks for confirmation; pass --yes to skip it. It also needs a confirmation in your browser: the CLI prints a link and a code (and opens the link), you approve with your passkey, and the command continues. --yes does not skip this, and CI tokens cannot do it at all.

$ deplane apps destroy my-old-app --yes
Destroyed my-old-app.
{
  "destroyed": "my-old-app"
}

deplane destroy

Destroy this folder's app, or the one named by --app. Same as deplane apps destroy, including the browser confirmation.

$ deplane destroy --app my-shop --yes
Destroyed my-shop.
{
  "destroyed": "my-shop"
}

deplane status

Show the app's URL, release, instances, health, the last build, and the last error with its fix.

$ deplane status
App:       my-shop (active)
URL:       https://my-shop.deplane.app
Release:   v3
Instances: 0 of 1 ready, size small
State:     Failed
Problem:   HealthCheckFailed: The app started but did not pass its health check on port 3000. Make sure it listens on $PORT.
Fix:       Make the app listen on 0.0.0.0 at the port in $PORT, and make health_check (if set) answer 200. Run `deplane logs` to see why it failed.
Last build: bld_9t2w6r1y8u3i5o7p4a0s, succeeded

State is Pending (no release yet), Deploying, Running, or Failed.

{
  "app": "my-shop",
  "url": "https://my-shop.deplane.app",
  "status": "active",
  "release": 2,
  "phase": "Running",
  "reason": "",
  "message": "1 of 1 instances ready.",
  "fix": "",
  "instances": 1,
  "ready_instances": 1,
  "size": "small",
  "last_build": {
    "id": "bld_9t2w6r1y8u3i5o7p4a0s",
    "status": "succeeded",
    "builder": "railpack",
    "position": 0,
    "image_digest": "sha256:0b9e7d61a2c3...",
    "release": 2,
    "created_at": "2026-10-13T09:40:00Z",
    "finished_at": "2026-10-13T09:41:10Z",
    "seconds": 70,
    "failure": null
  }
}

reason is empty, or one of Unschedulable, ImagePullFailed, HealthCheckFailed, Crashing, Timeout, and ReleaseCommandFailed. last_build is null before the first build. A failed build has failure set to {"code", "message", "fix"}.

deplane scale

Change the number of instances, the size, autoscaling, or when the app sleeps: deplane scale [N] [--size small|medium|large] [--max M] [--sleep 5m|15m|60m|off]. deplane scale 0 stops the app without deleting it. The change starts at once; it does not create a release.

FlagWhat it does
--size SIZEsmall, medium, or large. See Limits.
--max MAutoscale between N and M instances. --max 0 turns autoscaling off.
--sleep TIMEPut the app to sleep after 5m, 15m, or 60m without requests; the next request wakes it. off turns sleeping off. On Free, only 5m or 15m (limit_sleep).

deplane status shows the autoscale range, and Sleeping since HH:MM UTC while the app sleeps. The JSON for scale, apps, and status includes max_instances (null when off), sleep_after_minutes, sleep_state, and slept_at.

$ deplane scale 2 --size medium
my-shop now runs 2 medium instance(s).

Autoscale between 1 and 5 instances and sleep after 15 idle minutes (see Scaling):

$ deplane scale 1 --max 5 --sleep 15m

The JSON is the app, as in deplane apps:

{
  "id": "app_7k2m9x4q1w8e5r3t6y0u",
  "name": "my-shop",
  "region": "fra",
  "status": "active",
  "url": "https://my-shop.deplane.app",
  "current_release": 2,
  "instances": 2,
  "size": "medium",
  "port": 3000,
  "health_check": {"type": "tcp"},
  "created_at": "2026-10-13T09:12:00Z"
}

deplane releases

List the app's releases, newest first (up to 50). A release is a build plus its settings and secret names. STATUS is pending, active, superseded (it was live and a newer release took over), replaced (a newer release took over before it went live), or failed.

$ deplane releases
VERSION  STATUS      IMAGE         CREATED           DESCRIPTION
v3       failed      7a1b2c3d4e5f  2026-10-13 10:02  Deploy (HealthCheckFailed)
v2       active      0b9e7d61a2c3  2026-10-13 09:41  Deploy
v1       superseded  4f1c2a9e8b7d  2026-10-13 09:12  Deploy
{
  "releases": [
    {
      "version": 2,
      "status": "active",
      "image_digest": "sha256:0b9e7d61a2c3...",
      "description": "Deploy",
      "failure_reason": "",
      "created_at": "2026-10-13T09:41:00Z"
    }
  ]
}

When any release has a release command, a RELEASE COMMAND column shows its state: pending, running, succeeded, failed, skipped, or - for none. With --json, each release has release_command_status and, when it failed, release_command_output (its last lines).

Descriptions are Deploy, Set secrets NAMES, Unset secrets NAMES, Rollback to vN, or Automatic rollback to vN: X% of requests failed after vM (see Automatic rollback). In --json, automatic_rollback is true for a release Deplane made by rolling back automatically, and released_at is when a release went live.

deplane rollback

Roll back to the previous release that ran successfully, or to a version: deplane rollback [VERSION]. Both 3 and v3 work. A rollback creates a new release from the earlier release's build and settings. Asks for confirmation; pass --yes to skip it.

$ deplane rollback 1 --yes
Created release v4 (Rollback to v1). Run `deplane status` to follow the rollout.
{
  "version": 4,
  "status": "pending",
  "image_digest": "sha256:4f1c2a9e8b7d...",
  "description": "Rollback to v1",
  "failure_reason": "",
  "created_at": "2026-10-13T10:05:00Z"
}

Automatic rollback

When a new release starts failing right after it goes live, Deplane rolls it back for you. For the first 10 minutes after a release goes live, Deplane checks the errors your app returns every minute and rolls back to the previous good release when all of these hold:

Only 5xx responses your app returned count. 502, 503 and 504 do not: Deplane's router sends those itself when no instance answers in time, which a flood of requests or a rollout can cause. Requiring several networks means one visitor who keeps opening a page that fails cannot roll your app back on their own. To count networks, Deplane keeps a keyed hash of each network that got an error, never the address, for one day. The errors alert still counts every 5xx.

The rollback is a new release, exactly like deplane rollback, described as Automatic rollback to v1: 42% of requests failed after v2. Your team's alert channels get "Rolled back my-shop to v1", and it shows on Alerts. If the GitHub commit's status said success, it becomes a failure. Deplane never rolls back the same release twice, and never rolls back an automatic rollback; deplane rollback without a version skips a release that was rolled back automatically. If the rollback's own release does not start, the release before it keeps serving and your team gets "Automatic rollback of my-shop failed: still on v2" with the reason. It is on for every app; turn it off with deplane config --auto-rollback off or in the app's Settings tab.

deplane config

Show the app's port, health check, automatic rollback, release command and private network. Three settings can be changed here, and they apply right away (the release command from the next deploy). The port and health check come from deplane.toml and apply from the next deploy.

FlagWhat it does
--auto-rollback on or offTurn automatic rollback on or off.
--release-command "CMD"Set the release command that runs before each new build serves. --release-command "" removes it. It is one line, and it takes effect with the next deploy.
--private-network on or offLet the team's other apps reach this app at http://APP: see Private networking. It cannot be on while the app sleeps (private_network_sleep), and a pull request preview cannot use it.

The flags can be combined in one command. Another value than on or off fails with invalid_auto_rollback or invalid_private_network.

$ deplane config
Port             8080
Health check     HTTP /healthz
Auto rollback    on
Release command  python manage.py migrate
Private network  on, at http://my-shop

$ deplane config --auto-rollback off
Automatic rollback is off for my-shop. Roll back by hand with `deplane rollback`.

$ deplane config --release-command "npm run migrate"
Release command for my-shop: npm run migrate
It runs before each new build's release starts serving, beginning with your next deploy.

$ deplane config --private-network on
Private network is on for my-shop: your team's other apps reach it at http://my-shop.

With --json, all of them print the app, including "auto_rollback": false, "release_command": "npm run migrate", "private_network": true, and "private_url": "http://my-shop" (empty when the private network is off).

deplane open

Open the app's URL in your browser and print it. Without a terminal it only prints it.

$ deplane open
https://my-shop.deplane.app
{
  "url": "https://my-shop.deplane.app"
}

deplane logs

Show the app's recent log lines, or follow them live.

FlagWhat it does
-f, --followKeep streaming new lines until you press Ctrl-C.
--tail NLines per instance, from 0 to 1000. Default 100.
--process NAMEOnly the lines of one process: web, a worker or cron job's name, or release for the release command. With a run's ID (run_...), only that run's output.
$ deplane logs --tail 2
x7k2p | ready - started server on 0.0.0.0:3000
x7k2p | GET / 200 in 12ms

Each line starts with the short name of the instance that wrote it. With --json, each line is its own JSON object:

{"instance": "x7k2p", "line": "ready - started server on 0.0.0.0:3000"}
{"instance": "x7k2p", "line": "GET / 200 in 12ms"}

Your organization can follow at most 3 live streams at once; see limit_log_streams.

deplane run

Run a command once in the app's live release and show its output: deplane run [--app NAME] [--size SIZE] [--no-wait] -- COMMAND ARGUMENTS.

FlagWhat it does
--size SIZEThe size to run at: small (default), medium or large, up to your plan's largest.
--no-waitStart the run and return at once, printing its ID.

Everything after -- is the command, and each word reaches it as you typed it: spaces, quotes, $ and ; are kept, not interpreted. To use a pipe, && or variables, run a shell yourself: deplane run -- sh -c "cmd1 && cmd2".

The command runs in the release that was live when the run started, with the app's variables and database access, and no disk. It does not take traffic, and it has no input and no terminal: an interactive shell is not available. Its output goes to stdout, and the progress to stderr. The exit code of deplane run is the command's. A run that ended without one, because it was cancelled, ran past its hour, or found no room to start, exits with 1 and says why. When the team's slots are busy, a run waits up to an hour for a free one before it starts. Only the first 10 MB of output is shown; past that, deplane run says so and waits for the command to finish. Press Ctrl-C to cancel the run; it exits with 130.

$ deplane run -- python manage.py seed
Started run run_4m8q2k7x9c1v5b3n6z0a in my-shop (release v3).
seeded 3 rows

$ deplane run --size medium -- sh -c "python backfill.py && echo done"
$ echo $?
0

$ deplane run --no-wait -- node cleanup.js
Started run run_9t2w6r1y8u3i5o7p4a0s in my-shop (release v3).
See its output while it runs with `deplane logs -f --process run_9t2w6r1y8u3i5o7p4a0s --app my-shop`.

With --json, the output goes to stderr and stdout gets one document, the finished run (with --no-wait, the started one):

{
  "id": "run_9t2w6r1y8u3i5o7p4a0s",
  "app": "my-shop",
  "release": 3,
  "command": "python manage.py seed",
  "size": "small",
  "status": "succeeded",
  "exit_code": 0,
  "message": ""
}

status is pending, running, succeeded, failed or cancelled. The output is shown while the run exists and for an hour after it ends; it is not archived. A run lasts at most an hour. An app can have 2 runs at once and a team 5, a team can start 30 an hour, and a team's runs and release commands share 2 slots at once. The refusals are run_busy, run_limit, run_rate_limited, run_no_release and run_on_preview. You and CI tokens can run commands; AI tools cannot.

Secrets

See Secrets for how secrets work.

deplane secrets set

Set one or more secrets: deplane secrets set NAME=VALUE [NAME=VALUE...]. Changing a runtime secret on an app that has a release creates a new release.

FlagWhat it does
--buildSet build secrets, used only while building.
$ deplane secrets set DATABASE_URL=postgres://u:p@db.example.com/shop API_KEY=sk_live_123
Set runtime secret(s) API_KEY, DATABASE_URL.
Created release v5 with the new secrets.
{
  "names": ["API_KEY", "DATABASE_URL"],
  "scope": "runtime",
  "release": 5
}

release is null when no release was created: for build secrets, and for apps that have not been deployed yet.

deplane secrets unset

Remove secrets: deplane secrets unset NAME [NAME...]. Pass --build to remove build secrets.

$ deplane secrets unset API_KEY
Removed runtime secret(s) API_KEY.
Created release v6 without them.
{
  "names": ["API_KEY"],
  "scope": "runtime",
  "release": 6
}

deplane secrets list

List secret names and when they changed. Values are never shown.

$ deplane secrets list
NAME          SCOPE    UPDATED
NPM_TOKEN     build    2026-10-13 09:30
DATABASE_URL  runtime  2026-10-13 09:50
{
  "secrets": [
    {"name": "DATABASE_URL", "scope": "runtime", "updated_at": "2026-10-13T09:50:00Z"},
    {"name": "NPM_TOKEN", "scope": "build", "updated_at": "2026-10-13T09:30:00Z"}
  ]
}

GitHub

Connect a repository in the dashboard (an app's Settings, or Import from GitHub on New app). See GitHub.

deplane github

Show the app's connected repository, its branch and root folder, whether pull requests get previews (and their own databases), the open previews with their databases, and the last problem deploying from GitHub, if any.

$ deplane github
Repository: acme/shop
Branch:     main (every push deploys)
Root:       /
Previews:   on, for pull requests from the repository
  #12 Faster checkout  https://shop-pr-12.deplane.app  Running

deplane github deploy

Deploy the latest commit of the connected branch now, without a push, and follow it like deplane deploy.

deplane previews

List the app's live pull request previews and the pull requests waiting for one, with why each waits. See Choose which pull requests get previews.

$ deplane previews
Previews (3 of 3):
  #12 Faster checkout  https://shop-pr-12.deplane.app  Running
  #15 New footer  https://shop-pr-15.deplane.app  Running
  #18 Fix tax  https://shop-pr-18.deplane.app  Running
Waiting for a preview:
  #21 Dark mode  (the app's previews were full)
The app has 3 previews: build one with `deplane previews build PR --replace PR`.

With --json: {"previews": [...], "waiting": [{"pull_request", "title", "author", "branch", "sha", "reason", "refused_at"}], "max_previews": 3}. reason is slots_full, plan_apps, plan_resources, capacity_full, replaced or removed.

deplane previews build

Build a waiting pull request's preview from its latest commit: deplane previews build 21. When the app already has 3 previews, add --replace PR to delete one first, in the same step: deplane previews build 21 --replace 12. Replacing asks for confirmation (--yes without a terminal), and says when the replaced preview's database is deleted with it. If the new preview is still refused, nothing changes. Errors: preview_slots_full, preview_not_waiting, preview_not_found, previews_off.

{"pull_request": 21, "replaced": 12, "preview": {"name": "shop-pr-21", "url": "https://shop-pr-21.deplane.app"}}

deplane previews remove

Delete a pull request's preview: deplane previews remove 12. The pull request waits for a preview again. Asks for confirmation (--yes without a terminal). With --json: {"removed": 12}. In a pipeline's stop job (with DEPLANE_ID_TOKEN or BITBUCKET_STEP_OIDC_TOKEN) it removes the preview of that merge or pull request without asking; a preview already gone is not an error.

GitLab

An app can be connected to one GitLab project with your own fine-grained personal access token, in the dashboard (the app's Settings, GitLab): every push to its branch deploys, and merge requests get previews. The token is sealed, renewed every 7 days, and revoked when you disconnect.

deplane gitlab

Show the app's connected GitLab project, its branch, the token's account and expiry, whether the webhook is set up, and the merge request previews. With --json, the API's answer.

deplane gitlab deploy

Deploy the head of the connected branch now and follow the build.

Bitbucket

An app can be connected to one Bitbucket repository with a repository access token with only Repositories: Read and Pull requests: Read, in the dashboard (the app's Settings, Bitbucket). Deplane makes the webhook's secret and shows it once. Bitbucket cannot revoke a token from outside: when you disconnect, delete the token and the webhook on Bitbucket.

deplane bitbucket

Show the app's connected Bitbucket repository, its branch, whether the webhook is set up, and the pull request previews. With --json, the API's answer.

deplane bitbucket deploy

Deploy the head of the connected branch now and follow the build.

GitLab CI and Bitbucket Pipelines

A job deploys with its CI host's signed ID token: set DEPLANE_ID_TOKEN (GitLab) or use BITBUCKET_STEP_OIDC_TOKEN (Bitbucket), leave DEPLANE_TOKEN unset, and run deplane deploy --app NAME [--path DIR]. From the trust's deploy branch it deploys the app; from other branches deplane deploy --preview N deploys the preview of merge or pull request N. A pipeline only uploads: it never creates the app or changes its settings.

deplane pipelines trust

Trust a repository's jobs to deploy the app: deplane pipelines trust gitlab --app web --project 123456 --branch main [--path group/project] [--no-previews], or deplane pipelines trust bitbucket --app web --workspace '{uuid}' --repository '{uuid}' --name workspace/repo --branch main [--no-previews | --preview-database]. The repository is named by its IDs, never its path. Previews from other branches are on unless --no-previews; --preview-database gives each its own empty database, as GitHub previews can. At most 3 per app. Needs your passkey.

deplane pipelines list

List the app's pipeline trusts with their ID, repository, branch, previews and last use.

deplane pipelines remove

Remove a trust: deplane pipelines remove plt_.... Its pipelines stop deploying at once. Asks for confirmation (--yes without a terminal).

Assistant

deplane ask

Ask the assistant a question about your apps. In a folder with a deplane.toml, or with --app, that app is the one it may change; without one it only reads and explains. It makes ordinary changes itself; before one that takes something away it asks Run this? [y/N] (a delete: type the name), and --yes never skips that. Without a terminal it changes nothing and exits 1 if the answer needs a change, listing the commands. It never sees variable values or commands. See Assistant.

$ deplane ask "why did my last deploy fail?"
The build failed in `npm install`: package.json asks for react@19.4.0, which does not exist yet.
Change it to ^19.2.0 and deploy again.

Alerts

Rules and channels (Slack, Discord, webhooks; email is not delivered yet) are set in the dashboard. See Alerts.

deplane alerts

List your team's firing alerts, then recent ones.

$ deplane alerts
1 firing:
STATE     ALERT                                                    STARTED
FIRING    shop is down: 0 of 2 instances ready: CrashLoopBackOff  Oct 14 12:03
resolved  shop is slow: p95 response time above 2000 ms for 5 minutes  Oct 13 18:40

Usage

deplane usage

Show what your team used: requests, bandwidth, CPU, memory, build minutes, database disk and app disks, then each app, database and disk. Pick the period with --range: 24h, 7d, 30d, month (the default, since the 1st) or prev_month. Dates are in UTC. Build minutes are always this month's, counted against your plan. Deleted apps still show what they used, marked with *. Databases and disks have no requests, bandwidth or builds, so those columns show –; disks have no CPU or memory either. DISK GB-H is the storage a database or disk held. Add --json for the full data, including per-day or per-hour series.

$ deplane usage
Usage this month (Oct 1 – Oct 18)
  Requests        12,345
  Bandwidth out   1.2 GB
  CPU             3.4 hours
  Memory          120.5 GB-hours reserved (31.0 used)
  Build minutes   12.5 of 300
  Database disk   4,200 GB-hours
  App disks       120 GB-hours

APP        REQUESTS  BANDWIDTH  CPU H  MEMORY GB-H  BUILD MIN  DISK GB-H
shop       12,000    1.1 GB     3.0    100.0        10.0       –
old-api*   345       100.0 MB   0.4    20.5         2.5        –
shopdb     –         –          0.2    4.0          –          4200.0
shop disk  –         –          –      –            –          120.0
* deleted

Custom domains

Serve an app at your own domain, with HTTPS. Deplane routes a domain only after DNS proves it points at the app, then gets its certificate automatically.

deplane domains

List the app's custom domains: waiting for DNS, issuing certificate, or active.

$ deplane domains
DOMAIN            STATE
www.example.com   active
example.com       waiting for DNS

deplane domains add

Connect a domain and show the DNS records to set. For a subdomain such as www.example.com, add a CNAME to <app>.deplane.app. A root domain such as example.com cannot have a CNAME: add an A record to the address shown instead. Either way, add the TXT record _deplane-challenge.<domain> with the value shown, which proves the domain is yours. An app can have up to 10 domains.

$ deplane domains add www.example.com
www.example.com is waiting for DNS. No DNS record for www.example.com yet. Add a CNAME record pointing to shop.deplane.app and a TXT record _deplane-challenge.www.example.com with the value dpl-… (DNS changes can take a few minutes).

Set these records at your DNS provider (the TXT record proves the domain is yours):
  CNAME  www.example.com                     shop.deplane.app
  TXT    _deplane-challenge.www.example.com  dpl-…

deplane domains check

Check a domain's DNS and certificate now. Deplane also checks every 2 minutes for 48 hours after you add it.

deplane domains remove

Disconnect a domain. It stops serving the app right away. Asks for confirmation; pass --yes in scripts.

deplane domains switch

Set a domain's DNS records on Cloudflare for you: deplane domains switch DOMAIN [--provider cloudflare] [--check] [--yes] [--no-wait] [--rollback FILE]. It points the domain at the app (a CNAME, or A records to Deplane where a CNAME cannot go) and adds the TXT record that proves it is yours, adding the domain to the app first if needed. --check shows the records now and each change, with conflicting and proxied records flagged, and changes nothing. Without it, after a confirmation, it saves the current records to deplane-dns-DOMAIN-DATE.json, adds the TXT record and waits until public DNS shows it, changes the records at the domain all together (Cloudflare's batch API), and waits until Deplane verifies the domain; --no-wait skips the waits. --rollback FILE puts the records back exactly. MX records and every other name in the zone are never changed. See Switch DNS on Cloudflare.

The Cloudflare API token (Edit zone DNS, for this zone only) is read from stdin or CLOUDFLARE_API_TOKEN, never a flag. The CLI makes the Cloudflare calls itself: the token is never sent to Deplane, printed or written, and --debug shows Cloudflare requests as method, path and status only.

$ export CLOUDFLARE_API_TOKEN=...
$ deplane domains switch www.example.com --check
$ deplane domains switch www.example.com --yes
Switched www.example.com on Cloudflare (3 changes). To undo: deplane domains switch www.example.com --rollback deplane-dns-www.example.com-20261031-140501.json

Forms

Forms marked data-auto-form in your HTML, or listed under [[forms]] in deplane.toml. See Forms. AI tools can only list forms (the list_forms tool): submissions are your visitors' personal data (forms_private). Members read and export; only owners delete submissions or change notifications and CAPTCHA (owners_only).

deplane forms

List the app's forms: their submission and spam counts, CAPTCHA, the deployed version's fields, and the path each posts to.

$ deplane forms
FORM     SUBMISSIONS  SPAM  CAPTCHA  FIELDS          POSTS TO                                STATE
contact  12           3     off      email, message  /__auto_forms/3f9c2a1b7d4e5f60a1b2      active

deplane forms submissions

Show a form's newest 100 submissions, or its spam with --status spam: deplane forms submissions FORM [--status spam].

deplane forms export

Write every submission that is not spam, with every field any version of the form had, as CSV on stdout: deplane forms export FORM > FILE.csv.

deplane forms delete

Delete one submission, or with no ID all of a form's, spam included: deplane forms delete FORM [ID] [--yes]. It cannot be undone.

deplane forms notify

Show or set where a form's submissions are announced: deplane forms notify FORM [--email A,B|off] [--slack -|off] [--webhook -|off]. Email goes only to team members, as digests; Slack and webhooks get each submission. URLs are read from stdin (-), never taken as arguments. A new webhook's signing secret is shown once.

$ pbpaste | deplane forms notify contact --slack -
Email digests: you@example.com
Slack: on
Webhook: off

deplane forms captcha

Turn Cloudflare Turnstile on or off for a form: deplane forms captcha FORM on|off [--secret -|off]. Without --secret, the form keeps the key it has; --secret - reads your own Turnstile secret from stdin (needed on a custom domain); --secret off goes back to the platform's key, which works on the app's own address only.

Databases

Managed Postgres (17, or 18 where Deplane offers it) next to your apps. Each database runs in its own microVM on a 10 GB disk that only it uses, reachable only by your team's apps. Attaching it to an app sets the app's DATABASE_URL, a write-only variable, and rolls out a new release. A database counts against your plan as one instance of its size.

deplane db

List your team's databases, their Postgres version, their state, whether each has a live copy on another server, and the apps they are attached to. COPY is live (the copy follows the database), none (the copy is down because its server is; it comes back when the server returns) or – (no copy to show, as for a pull request preview). See Live copy and failover.

deplane db create

Create a database. It is ready in about a minute. --size is small (default), medium or large. --version is its Postgres major version: 17 (the default) or 18 where Deplane offers it (otherwise refused with postgres_version_unavailable). A database keeps its version for its whole life: there is no in-place upgrade (see Postgres versions).

$ deplane db create main
$ deplane db create next --version 18
$ deplane db attach main --app shop

deplane db attach

Give an app the database as DATABASE_URL (postgres://app:…@pg--main:5432/app). An app uses one database at a time.

deplane db detach

Remove the app's DATABASE_URL. The database and its data stay.

deplane db destroy

Delete a database and all its data, after detaching it from its apps. Asks for confirmation; pass --yes in scripts. It also needs a confirmation in your browser: the CLI prints a link and a code (and opens the link), you approve with your passkey, and the command continues. --yes does not skip this, and CI tokens cannot do it at all. Its backups are kept for your plan's backup window; restore it with deplane db restore NAME --deleted --at latest.

deplane db backups

Show a database's backup status (Protected, Behind, Starting or Off), the window it can be restored to (your local time and UTC; gaps show as more than one window), the last full copy, the space used and recent restores. --deleted shows a deleted database's backups.

deplane db restore

Restore a database into a new database; the original is never changed. --at is latest, a time such as 2026-10-15 16:31 (your local time) or 2026-10-15T14:31:20Z, or a time ago: -10m, 2h, 1d. The new name is --to, or <name>-restored-<HHMM>. --deleted restores a deleted database. It waits up to 15 minutes, showing progress (--no-wait returns at once), then prints the deplane db switch command to run.

Restoring and switching need a confirmation in your browser with your passkey: the CLI prints a link and a code, waits for you to approve, and continues. Each restore or switch uses up its confirmation, so the next one asks again. CI tokens and AI tools cannot restore or switch databases.

$ deplane db restore main --at -10m Restoring from main as of 2026-10-15 14:42:31 UTC. main-restored-1442 is ready. Check it, then move your apps to it: deplane db switch main-restored-1442 --from main

deplane db switch

Give every app attached to --from this database as DATABASE_URL instead (a new release each, no downtime). The original database is kept until you delete it.

deplane db import

Copy a Postgres database from anywhere into a new Deplane database. The source is only read; nothing there is changed except, in live mode, a publication and a replication slot that Deplane removes when the import ends.

The source's URL is asked for at a hidden prompt, or read from stdin when no terminal is attached. It is never taken from an argument (it would land in your shell history), and deplane db import postgres://... is refused. The URL is never printed, and --debug hides it.

The command checks the source, prints what it found (size, tables, extensions, warnings, and whether copy and live are possible, with reasons), then asks for a name and a mode, starts, and follows the import. --name, --size, --mode and --exclude-schema answer those questions; --version sets the new database's Postgres version (left out: 17, or the source's own major when it is newer, so a Postgres 18 source goes into Postgres 18; asking for an older major than the source's is refused with source_newer_than_target); --yes skips the confirmation; --no-wait returns right after the start.

Without --mode, a terminal is asked (live is the default when it is possible); a script gets live when it is possible, otherwise copy, and the CLI says which. Without a terminal, --name and --yes are required. --exclude-schema a,b leaves out schemas by name, replacing the check's default list; --exclude-schema "" leaves nothing out. A live import stops following at "Syncing" and returns; --follow keeps watching it.

An import is for people: CI tokens and AI tools cannot run it. A team has one import at a time. The source can be at most 5 GB.

$ printf '%s' "$SOURCE_URL" | deplane db import --name shop --mode live --yes
Checking db.example.com:5432/shop ...
Checked db.example.com:5432/shop
  Postgres 16.4, 953 MB, 12 tables
  Largest: public.orders 572 MB
  Copy: possible
  Live: possible
Copying the data into shop ...
The data is copied. shop now follows the source.
Syncing: 12 KB behind, last change applied 10:00:14 UTC.
Keep your app on the source until you are ready. Then stop writes to the source and run `deplane db import finish`.

deplane db import status

Show an import: its state, source, new database and, while a live import is syncing, how far behind it is. ID defaults to the newest active import (or, with none, the newest). With --json, one document with the full report.

deplane db import finish

Finish a live import: the last changes are copied, every table's rows are compared with the source, and the link to the source is removed. Stop writes to the source first (scale your app to 0, or turn on maintenance mode); the CLI asks you to confirm that, and --yes skips the question. If the schema changed, the new database had not caught up, or a count differs, nothing is changed and the import goes back to syncing with the reason. ID defaults to the newest active import. --no-wait returns right after the request.

deplane db import cancel

Cancel an import, in any state before it is done. Copying stops, and Deplane removes what a live import created on the source. The new database stays: empty after a copy or a live import cancelled before its copy finished, with the data copied so far after a live import cancelled while in sync; delete it with deplane db destroy. Asks for confirmation; --yes skips it. If Deplane could not reach the source to clean up, the CLI prints the two SQL lines to run there. ID defaults to the newest active import.

Migrations

deplane import heroku

Move a Heroku app to Deplane: deplane import heroku HEROKU_APP [--source DIR] [--name NAME] [--check] [--yes]. See Moving from Heroku for what moves, what stays, and how to switch, or import from the dashboard instead (From the dashboard).

The command reads the Heroku app with a Heroku token (only GET requests, to api.heroku.com) and prints a report: what is copied, what is recreated and what stays on Heroku, with ✓, ⚠ and ✗ lines. Config var names are listed; values never are. When you are logged in, the report also checks your plan's limits (workers, sizes, apps, databases, Redis, instances, memory) against what your team uses, with read-only requests, and a limit the import would cross is a ✗ line. --check stops there and creates nothing; without a Deplane login it skips the plan and says so; --check --json prints the report as one JSON document. Without --check, after a confirmation (--yes skips it), it has Deplane check the Postgres database first, so a database Deplane cannot copy stops the import before anything is created, then creates the app (named --name, or after the Heroku app), sets the copied config vars as secrets, the release command, the web instances and size and the workers, copies the database (Postgres import, copy mode) and attaches it, creates and attaches a Redis, and deploys --source (default: the current folder) as deplane launch does. Only process types that run dynos on Heroku become workers. It ends with the URL and the next steps, including the switch-over with deplane db switch.

The Heroku token is read from stdin (a pipe, or a hidden prompt in a terminal) or from HEROKU_API_KEY. A piped token wins over the variable, which is used when nothing is piped in; the CLI says which one it used, never the token. There is no flag for it, and a token typed as the argument is refused (token_in_argument). It is sent only to Heroku: never to Deplane, never printed or written, and --debug shows Heroku requests as method, path and status only. Create a one-hour, read-only token for it:

$ heroku authorizations:create --scope read-protected --expires-in 3600 --short | deplane import heroku shop --check
$ heroku authorizations:create --scope read-protected --expires-in 3600 --short | deplane import heroku shop --yes
Reading the Heroku app shop (read-only) ...
...
Deployed shop v1: https://shop.deplane.dev

Imported the Heroku app shop into shop: https://shop.deplane.dev
Heroku still serves your users; nothing changed there.

If a step fails, the command stops, lists what it created, the commands to finish by hand and the commands to delete it, and exits with the failed step's code (6 for a plan limit, 3 for a failed build, 4 for a failed deploy). A report with a ✗ line exits with 1 (heroku_import_refused).

deplane import render

Move a Render web service, with the background workers and cron jobs built from the same repository, to Deplane: deplane import render SERVICE [--source DIR] [--name NAME] [--check] [--yes]. SERVICE is the service's ID (srv-...) or its name. See Moving from Render, or import from the dashboard instead (From the dashboard).

It works like deplane import heroku: a read-only check (only GET requests, to api.render.com), a report with ✓, ⚠ and ✗ lines that names variables but never shows their values, your plan's limits checked before anything is created, then, after a confirmation (--yes skips it), the app with its secrets, release command (Render's pre-deploy command), instances, size, workers and cron jobs, the Postgres database copied through its external URL (Postgres import, copy mode), a new Redis for a Key Value, and the deploy of --source. A database whose IP allow list does not let Deplane in is a ✗ line that names the addresses to add.

The Render API key is read from a pipe on stdin, then from RENDER_API_KEY, then from a hidden prompt in a terminal; a piped key wins. There is no flag for it, and a key typed as an argument is refused (token_in_argument). It is sent only to Render, never to Deplane, never printed or written. Render keys can change everything your account can: delete it when the import is done.

$ deplane import render shop --check
Render API key (input is hidden; create one in the Render Dashboard under Account Settings → API Keys):
Render service shop (web service, region frankfurt, repo github.com/acme/shop@main, commit 1a2b3c4) → Deplane app shop
...

A report with a ✗ line exits with 1 (render_import_refused); a failed step stops the import as for Heroku.

deplane import railway

Move a Railway service, with the worker and cron services of the same environment, to Deplane: deplane import railway PROJECT[/SERVICE] [--source DIR] [--name NAME] [--check] [--yes]. PROJECT is the project's name or ID; SERVICE is needed when more than one service has a public domain. See Moving from Railway, or import from the dashboard instead (From the dashboard).

It works like deplane import heroku: a read-only check (GraphQL queries only, to backboard.railway.com), the same report and plan check, then the app with its secrets, release command (the pre-deploy command), replicas, workers and cron jobs, the Postgres database copied through its public URL (DATABASE_PUBLIC_URL, which needs a TCP proxy on the Postgres service), a new Redis, and the deploy of --source. Sealed variables cannot be read through Railway's API; the report says so.

The token is read from a pipe on stdin, then from RAILWAY_TOKEN, then from a hidden prompt; a piped token wins. Use a project token (the project's Settings → Tokens): it reaches one environment of one project, and that environment is the one imported. An account token also works; the CLI then imports the production environment, or the only one. Workspace tokens are not supported yet. There is no flag for the token, and an argument that holds it is refused. It is sent only to Railway, never to Deplane, never printed or written.

$ RAILWAY_TOKEN=... deplane import railway shop --check
Railway service shop (project shop, environment production, region europe-west4-drams3a) → Deplane app shop
...

A report with a ✗ line exits with 1 (railway_import_refused).

deplane import compose

Move a Docker Compose project to Deplane: deplane import compose [-f FILE] [--prefix NAME] [--check] [--yes]. See Moving from Docker Compose for what each service becomes and what cannot move.

The command reads the compose file (-f FILE, default compose.yaml, compose.yml, docker-compose.yaml or docker-compose.yml in the current folder), fills in its ${VAR} variables from your environment and the .env beside it, reads its env_files, and prints a report with ✓, ⚠ and ✗ lines: services with ports: become apps (from their image:, or built from their build: folder), well-known Postgres, MySQL/MariaDB, Redis/Valkey and ClickHouse images become managed ones attached to the services that use them, services that share an app's image or build become its workers, other services become apps on the private network, named volumes become disks, and variables become secrets. Variable names are listed; values never are. Variables that pointed at a database (its host, URL, user, password or name) are filled in from the managed one on Deplane's servers, so its password never reaches your computer. When you are logged in, the report also checks your plan's limits against what your team uses, with read-only requests. --check stops there and creates nothing; --check --json prints the report as one JSON document.

Without --check, after a confirmation (--yes skips it), it creates the apps, sets their port, health check and private network, scales them, creates and attaches the databases, sets the secrets, adds the workers and disks, waits for the databases, and deploys each app: an image with deploy --image, a folder as deplane deploy does (writing deplane.toml there if it has none). --prefix NAME names the apps: the main app (the first with a port) is NAME, the others NAME-SERVICE; the default is the file's name:, else its folder's name. Data is not copied: the report says how to copy it.

$ deplane import compose --check
$ deplane import compose -f deploy/docker-compose.prod.yml --prefix shop --yes
...
Imported 1 app(s):
  shop  https://shop.deplane.dev

-f names the compose file only after import compose (and the stack file after stack); elsewhere it is --follow. If a step fails, the command stops, lists what it created, the commands to finish by hand and the commands to delete it, and exits with the failed step's code. A report with a ✗ line exits with 1 (compose_import_refused); nothing is created before that.

deplane import fly

Move a Fly.io app, with its process groups and scheduled Machines, to Deplane: deplane import fly APP [--source DIR] [--name NAME] [--check] [--yes]. See Moving from Fly.io, or import from the dashboard instead (From the dashboard).

It works like deplane import heroku: a read-only check (only GET requests, to Fly.io's Machines API at api.machines.dev), the same report and plan check, then the app with its variables as secrets, release command, instances, size, workers and cron jobs. fly.toml in --source is the main source: its [processes], [http_service] or [[services]] (internal_port), [deploy] release_command, [[mounts]] and [env]; the Machines give the counts and sizes (by memory: up to 512 MB small, 1 GB medium, 2 GB large). Fly.io never gives secret values through its API, so secrets are listed by name: the app is then created without being deployed, and the next steps say which secrets to set before deplane deploy. No database is copied: the report says how to move Fly Postgres.

The token is read from a pipe on stdin, then from FLY_API_TOKEN, then from a hidden prompt; a piped token wins. Use a read-only one: fly tokens create readonly --expiry 1h | deplane import fly shop --check. There is no flag for it, and an argument that holds it is refused. It is sent only to Fly.io, never to Deplane, never printed or written.

A report with a ✗ line exits with 1 (fly_import_refused).

deplane import vercel

Move a Vercel project, with its cron jobs, to Deplane: deplane import vercel [TEAM/]PROJECT [--source DIR] [--name NAME] [--check] [--yes]. PROJECT is the project's name or ID; TEAM is the team's slug or ID, also read from VERCEL_TEAM_ID or the folder's .vercel/project.json. See Moving from Vercel, or import from the dashboard instead (From the dashboard).

It works like deplane import heroku: a read-only check (only GET requests, to api.vercel.com), the same report and plan check, then the app with its production variables as secrets and as build secrets, a cron job for each Vercel cron (a request to the app's path, over the private network), and the deploy of --source. Secret (sensitive) variables cannot be read through Vercel's API: they are listed by name, and the app is then created without being deployed. The report says what does not move: Vercel Functions outside Next.js, vercel.json rewrites, redirects and headers, and Vercel's own services.

The token is read from a pipe on stdin, then from VERCEL_TOKEN, then from a hidden prompt; a piped token wins. There is no flag for it, and an argument that holds it is refused. It is sent only to Vercel, never to Deplane, never printed or written.

A report with a ✗ line exits with 1 (vercel_import_refused).

deplane import netlify

Move a Netlify site to Deplane: deplane import netlify SITE [--source DIR] [--name NAME] [--check] [--yes]. SITE is the site's name (or name.netlify.app) or ID. See Moving from Netlify, or import from the dashboard instead (From the dashboard).

It works like deplane import heroku: a read-only check (only GET requests, to api.netlify.com), the same report and plan check, then the app with its production variables as secrets (and the ones Netlify gives to builds as build secrets, with netlify.toml's), and the deploy of --source. Secret variables cannot be read through Netlify's API: they are listed by name, and the app is then created without being deployed. Functions, Edge Functions, redirects, Forms and Identity are listed, not moved.

The token (a personal access token) is read from a pipe on stdin, then from NETLIFY_AUTH_TOKEN, then from a hidden prompt; a piped token wins. There is no flag for it, and an argument that holds it is refused. It is sent only to Netlify, never to Deplane, never printed or written.

A report with a ✗ line exits with 1 (netlify_import_refused).

deplane import cpanel

Move a WordPress site from cPanel to Deplane: deplane import cpanel --host HOST --user USER [--site DOMAIN] [--name NAME] [--backup FILE] [--db-dump FILE] [--check] [--cutover] [--replace-live] [--yes]. See Moving from cPanel for what moves, what stays (email), and how to switch DNS. CLI only for now: the dashboard has no cPanel import yet.

The command reads the cPanel account with a cPanel API token (read-only calls to https://HOST:2083): the site's document root (--site, default the main domain), its wp-config.php and WordPress version, the PHP version, the database's size, and the domain's mailboxes and MX records. It prints a report with ✓, ⚠ and ✗ lines. Setting names are listed; values from wp-config.php (the database password, keys) never are. When you are logged in, the report also checks that the WordPress template fits your plan (a small app, a medium MySQL and a 5 GB disk; Free has no MySQL) and that the app name (--name, default from the domain: example.com becomes example-com) is free. --check stops there and creates nothing; --check --json prints the report as one JSON document.

Without --check, after a confirmation (--yes skips it), it has cPanel write a full account backup into your home directory, downloads it, and keeps only the site's files and database dump in a private temporary folder; sizes are checked here (site files up to 4.5 GB, database up to 5 GB), so a site that does not fit stops before anything is created. With --backup FILE it reads a backup you downloaded yourself instead (with --db-dump FILE for a home directory backup's database). Then it creates the app from the WordPress template with its MySQL and disk, turns on the app's one-time import receiver, replaces the app's files and database with the site's (site addresses rewritten to the app's own address, PHP serialized values kept valid), turns the receiver off, and prints the test address and the cutover checklist. While the files and database are replaced, the site shows a maintenance page. Running the same command again replaces the files and database of the app an earlier import made (of the same site only), after a disk backup and a MySQL backup.

--cutover copies the site again into that app without the address rewrite, sets the address to the real domain, and adds the domain and www. to the app, printing their DNS records. Run it when you are ready to change DNS. After a cutover the app is the live site: running the import or --cutover again asks you to type the domain (--yes does not skip that), or needs --replace-live (cpanel_live_site).

The token is read from stdin (a pipe, or a hidden prompt in a terminal) or from CPANEL_API_TOKEN. There is no flag for it, and a token typed as an argument is refused (token_in_argument). It is sent only to cPanel: never to Deplane or to the app, never printed or written, and --debug shows cPanel calls by name and status only.

$ deplane import cpanel --host server1.host.example --user shop --check < token.txt
$ deplane import cpanel --host server1.host.example --user shop --yes < token.txt
...
example.com's copy is live at https://example-com.deplane.app, with the site's files, database and users.
$ deplane import cpanel --host server1.host.example --user shop --cutover --yes < token.txt

A report with a ✗ line exits with 1 (cpanel_import_refused). If a step after the app was created fails, the command says how to run it again and how to delete what it made, and turns the import receiver off.

Stacks

Several apps, their workers, cron jobs, databases, disks, domains and variables, described once in stack.yaml and applied together. See Stacks for the file and what an apply changes or leaves. -f FILE picks the file (default: stack.yaml in the current folder). AI tools can read, check and plan stacks (the stacks and check_stack tools), never apply them.

deplane stack check

Check a stack file: deplane stack check [-f FILE]. It reads the file and reports every problem, not only the first, as ✗ (stops the stack) and ⚠ (worth reading) lines, each with the place in the file and the fix. When you are logged in, it then plans the file against your team, as deplane stack plan does; logged out, it checks on your computer only and says so. It changes nothing.

$ deplane stack check
✓ stack.yaml: stack shop, 2 services, 2 engines.

A file Deplane cannot read at all fails at once with the line (stack_invalid). A file with a ✗, or a plan that cannot be applied, exits with 1. --json prints {"valid": true|false, "problems": [{"path", "code", "message", "fix", "warning"}]}, with "plan" when logged in. A path: folder's deplane.toml fills in what the stack leaves out; where both set a field, the stack wins (set_in_both).

deplane stack plan

Show what applying the file would do to your team now: deplane stack plan [-f FILE] [--adopt] [--move] [--prune]. It changes nothing. Each object gets a line, + create, ~ update, = unchanged, → adopt, ⇢ move (with --move), - left (no longer in the file; it keeps running and stays in the stack until pruned), ✗ delete (with --prune) or ! refused, with the settings that change below it. Variables show names only, never values. Then come the ⚠ and ✗ lines, whether the stack fits your plan, and the values the apply will ask for.

$ deplane stack plan
= postgres shop-db (unchanged)
= app shop-web (unchanged)
~ app shop-api (update)
    instances: 1 → 2
    env.LOG_LEVEL: set by hand → set
⚠ services.shop-api.env.LOG_LEVEL: LOG_LEVEL was set by hand since the last apply; an apply with --yes sets it back.
  Keep the change in the file instead, or leave it out of the file to manage it by hand.

--adopt and --prune plan what deplane stack apply would do with them. --json prints the plan: stack, new, can_apply, changes, problems, steps, fit, prompts and kept. The command exits with 1 when the plan cannot be applied (a ✗ line, or it does not fit your plan), so a CI job can stop on it; --json still prints the plan.

deplane stack apply

Create or update the stack the file describes: deplane stack apply [-f FILE] [--adopt] [--move] [--prune] [--yes] [--no-wait]. It plans, prints the plan, asks for a confirmation (--yes skips it; when the plan deletes anything, with --prune, it asks you to type the stack's name instead, and --yes does not skip that; the apply is refused if it would then delete anything else, stack_plan_changed), asks for the values the file marks {prompt: true}, starts the apply on Deplane's servers and prints each step as it finishes. At the end it uploads and deploys the services whose source is a path: folder, as deplane deploy does.

$ deplane stack apply
Stack shop is new.
+ postgres shop-db (create)
...
Apply stack shop (18 steps)? [y/N] y
STRIPE_KEY for shop-web (hidden):
✓ postgres shop-db: create small
✓ app shop-web: create
...
Applied stack shop.

A {prompt: true} value comes from DEPLANE_STACK_<SERVICE>__<NAME> or DEPLANE_STACK_<NAME> when one is set (the CLI says which it used, never the value), else from a hidden prompt in a terminal; a shell variable that only has the same name is never read; there is no flag for it (stack_prompt_missing). Deplane sets it and does not keep it.

A plan with a ✗ stops before anything changes (stack_cannot_apply). A failed step stops the apply and exits with 1 (stack_apply_failed); fix the cause and run deplane stack retry. With --json, it prints the finished apply: id, stack, status, step, steps and message. A CI token can apply, but cannot connect a GitHub repository to an app for the first time (ci_token_not_allowed).

deplane stack list

List your team's stacks: name, who manages it (blueprint for a stack.yaml), how many apps and databases it holds, its revision (one more for each apply), and its last apply's status. --json prints the list.

$ deplane stack list
shop  blueprint  4 objects  revision 3  apply done

deplane stack status

Show one stack: deplane stack status NAME. Its apps with their state, ready instances and address, its databases, which objects joined with --adopt, and its last apply: its ID, status, the step it is on while running, and the message of a failed one. --json prints the stack with members, status and apply.

$ deplane stack status shop
Stack shop (blueprint, revision 3)
  postgres shop-db
  app shop-web  Running, 2 ready  https://shop-web.deplane.app
Last apply sta_8f2k...: failed
  The app shop-web was deleted while the stack was being applied. Apply again.

deplane stack export

Print a stack as stack.yaml: deplane stack export NAME [--format yaml]. It is built from the last file applied, and never contains a secret: values come back only when neither their name nor a password in a URL looks secret, the rest as {prompt: true}, generated values as {generate: N}. --format yaml is the only format for now (invalid_format).

$ deplane stack export shop > stack.yaml

deplane stack retry

Continue a failed apply from the step that failed: deplane stack retry APPLY-ID (deplane stack status NAME shows the ID). Steps already done are kept. After a failed build or release, apply again instead: a retry waits for the same build. The retry runs as you and prints each step as deplane stack apply does; --no-wait returns at once. Values asked for at apply time are not kept, so if the remaining steps need them, run deplane stack apply again instead (stack_retry_needs_values). Only a failed apply can be retried (stack_apply_not_failed). When deletes are still to run, it asks you to type the stack's name, as apply does.

deplane stack forget

Forget a stack: deplane stack forget NAME. Its apps and databases keep running, outside any stack; nothing is deleted. It first lists what it releases (no stack command deletes those afterwards), then asks (--yes skips the question). Only a person can forget a stack: CI tokens are refused (ci_token_not_allowed). A stack being applied cannot be forgotten until the apply ends (stack_apply_running).

deplane stack destroy

Delete what a stack created, then the stack: deplane stack destroy NAME. It lists the apps and databases that go, with their data, and asks you to type the stack's name (always, even with --yes). Then it needs a fresh confirmation with your passkey in the browser, and follows the deletes. Apps and databases that joined with --adopt are left running, outside any stack. CI tokens and AI tools cannot destroy a stack.

$ deplane stack destroy shop
This deletes, with their data: app shop-web, app shop-api, postgres shop-db, redis shop-cache.
Type the stack's name (shop) to confirm shop
Destroyed stack shop.

Team

Work on apps together. Everyone signs in with their own passkey, and everything they do appears in the activity log with their name. Members deploy and manage apps; owners can also invite and remove people.

deplane team

List your team's members and open invitations.

deplane team invite

Invite someone by email. You get a link to send them yourself: it works once, for that address, for 7 days, and they set up a passkey when they join. Needs a fresh confirmation with your passkey. --role owner makes them an owner.

$ deplane team invite colleague@company.com
Send this link to colleague@company.com. It works once, for 7 days, and is shown only now:

  https://app.deplane.io/invite?t=…

deplane team remove

Remove a member. Their tokens for the team stop working. Asks for confirmation; pass --yes in scripts. Needs a fresh confirmation with your passkey.

Workers and cron jobs

Run more processes from the same app: workers that run all the time (a queue consumer, a bot) and cron jobs that run on a schedule (a nightly report). They use the app's current image and its variables, each in its own microVM, and their output appears in deplane logs. The command runs through the image's shell (/bin/sh -c). Workers count against your plan's instances and memory; a cron job counts as one instance of its size. Free has cron jobs but no workers: adding a worker, or starting or growing one, is refused with limit_sleep; a worker from before a move to Free can be scaled down or removed.

deplane processes

List the app's workers and cron jobs.

$ deplane processes
NAME     KIND    RUNS            SIZE   COMMAND
emails   worker  1 instance(s)   small  node emails.js
nightly  cron    0 3 * * * UTC   small  node report.js

deplane processes add

Add a worker, or with --schedule a cron job. Schedules have five fields in UTC (minute, hour, day of month, month, day of week), or @hourly, @daily, @weekly, @monthly. A cron job runs one instance at a time, for at most an hour, and a failed run is not retried.

$ deplane processes add emails --command "node emails.js"
$ deplane processes add nightly --command "node report.js" --schedule "0 3 * * *"

deplane processes scale

Change a worker's instances (0 stops it), or any process's --size, --command, or --schedule.

deplane processes remove

Remove a worker or cron job. Asks for confirmation; pass --yes in scripts.

App disks

A 5 GB disk for an app, mounted at a path, whose files stay across deploys and restarts. An app with a disk runs one instance and does not autoscale, and each deploy stops it for a few seconds. See Disks.

deplane disks create

Give an app a disk: deplane disks create APP [--path /data]. The app must run one instance without autoscaling; if it runs more, scale it first with deplane scale 1 --max 0 --app APP.

$ deplane disks create my-app
Added a 5 GB disk to my-app at /data. Files there survive deploys and restarts, and are backed up every night.
my-app now runs one instance and cannot autoscale. Each deploy stops it for a few seconds while the new release takes the disk.

deplane disks list

List your team's disks with their path and last backup. --json prints {"disks": [...]}.

deplane disks backups

List an app's disk backups, newest first: ID, status, nightly or on request, start time (UTC), and size.

deplane disks backup

Back up an app's disk now. At most once an hour; the app keeps running.

deplane disks restore

Replace all files on an app's disk with a backup: deplane disks restore APP BACKUP-ID. Files written since that backup are lost, and the app stops while the restore runs. Asks for confirmation; pass --yes in scripts. It also needs a confirmation in your browser with your passkey, each time; CI tokens and AI tools cannot restore disks.

deplane disks destroy

Destroy an app's disk and the files on it. Asks for confirmation; pass --yes in scripts. Like restore, it needs your passkey in the browser each time. Its backups are kept for 3 days.

deplane templates list

Lists the templates with what each needs (memory, Postgres, Redis, MySQL, ClickHouse, a disk) and whether your team can deploy it now: ready, "no room now" (free some room: the message says how much is needed and free) or "needs a larger plan". Below, your team's recent template deploys with their ID, status and step. --category Starters|Analytics|Monitoring|Productivity|Developer tools|Data narrows the list. --json prints both lists.

deplane templates show

deplane templates show NAME shows one template: its description, what it needs, whether your team has room, its licence, source and version, the variables it sets (names only) and the trademark notice.

deplane templates retry

deplane templates retry DEPLOY-ID continues a failed template deploy from the step that failed. What was already made is kept and its generated secrets stay the same; at the last step it builds again.

deplane templates credentials

deplane templates credentials DEPLOY-ID shows, once, the admin sign-in a template set up at deploy time (for example Grafana's generated admin password), for a deploy you started and that is done. deplane launch --template prints it by itself when it waits for the deploy; use this for a deploy started with --no-wait, from the dashboard or by an AI tool. Deplane never shows it a second time (template_credentials_shown).

deplane templates delete

deplane templates delete DEPLOY-ID deletes the app (with its disk) and the database, Redis, MySQL and ClickHouse database that this deploy made, and nothing else; one that another app uses is kept. It asks first (--yes skips the question) and needs your browser confirmation, like other deletions. A running deploy cannot be deleted; wait for it to finish or fail.

Redis

A managed Redis (Valkey 8, 256 MB) for caches and queues. See Redis.

deplane redis create

Create a Redis: deplane redis create NAME. It counts 256 MB against your plan's memory.

deplane redis list

List your team's Redis with their address, apps and last backup. --json prints {"redis": [...]}. Passwords are never shown.

deplane redis attach

Give an app the Redis as REDIS_URL: deplane redis attach NAME --app APP. A new release rolls out. An app has one Redis.

deplane redis detach

Remove an app's REDIS_URL: deplane redis detach NAME --app APP. The Redis and its data are kept.

deplane redis backups

List a Redis's backups, newest first: ID, status, nightly or on request, start time (UTC), size.

deplane redis backup

Back up a Redis now. At most once an hour; it keeps running.

deplane redis restore

Replace a Redis's data with a backup: deplane redis restore NAME BACKUP-ID. Data written since the backup is lost, and the Redis stops while the restore runs. Asks for confirmation; pass --yes in scripts. It also needs your passkey in the browser each time; CI tokens and AI tools cannot restore.

deplane redis destroy

Destroy a Redis and its data; its apps lose REDIS_URL. Asks for confirmation; pass --yes in scripts. Needs your passkey each time. Its backups are kept for 3 days.

MySQL

A managed MySQL 8.4 (medium or large, 10 GB) for apps that need MySQL. See MySQL.

deplane mysql create

Create a MySQL: deplane mysql create NAME [--size medium|large]. Without --size it is medium (1 GB). It counts as one instance of its size against your plan.

deplane mysql list

List your team's MySQL with their status, size, apps and last backup. --json prints {"mysql": [...]}, with each one's host, port and version. Passwords are never shown.

deplane mysql attach

Give an app the MySQL as MYSQL_URL: deplane mysql attach NAME --app APP. A new release rolls out. An app has one MySQL.

deplane mysql detach

Remove an app's MYSQL_URL: deplane mysql detach NAME --app APP. The MySQL and its data are kept.

deplane mysql backups

List a MySQL's backups, newest first: ID, status, nightly or on request, start time (UTC), size. deplane mysql backups --destroyed lists instead the backups of your team's destroyed MySQL, with the name each came from; you can restore them into another MySQL until they expire.

deplane mysql backup

Back up a MySQL now. At most once an hour; it keeps running.

deplane mysql restore

Replace a MySQL's data with a backup: deplane mysql restore NAME BACKUP-ID. The backup can be from any MySQL of your team, including a destroyed one. Data written since the backup is lost, and the MySQL stops while the restore runs. Asks for confirmation; pass --yes in scripts. It also needs your passkey in the browser each time; CI tokens and AI tools cannot restore.

deplane mysql destroy

Destroy a MySQL and its data; its apps lose MYSQL_URL. Asks for confirmation; pass --yes in scripts. Needs your passkey each time. Its backups are kept for 3 days at most; deplane mysql backups --destroyed lists them.

There is no command to open a MySQL session from your computer: the MySQL has no public address. To run SQL, use deplane run in an app whose image has a client; see MySQL.

ClickHouse

A managed ClickHouse 26.8 database (large, 2 GB memory, 10 GB) for analytics and event data. See ClickHouse®.

deplane clickhouse create

Create a ClickHouse database: deplane clickhouse create NAME [--size large]. Large is the only size, and the default. It counts as one large instance against your plan and needs 2 GB of your plan's memory.

deplane clickhouse list

List your team's ClickHouse databases with their status, size, disk used (as of the last backup), apps and last backup. If your plan cannot hold one, it says what to upgrade, and links https://deplane.io/pricing. --json prints {"clickhouse": [...], "plan": {"allowed", "fits", "message"}}, with each one's host, ports and version. Passwords are never shown.

deplane clickhouse attach

Give an app the ClickHouse database as CLICKHOUSE_URL: deplane clickhouse attach NAME --app APP. A new release rolls out. An app has one ClickHouse database.

deplane clickhouse detach

Remove an app's CLICKHOUSE_URL: deplane clickhouse detach NAME --app APP. The ClickHouse database and its data are kept.

deplane clickhouse backups

List a ClickHouse database's backups, newest first: ID, status, nightly or on request, start time (UTC), size, and why one failed. It ends with the disk used at the last backup, against the 4.5 GB line above which backups have no room. deplane clickhouse backups --destroyed lists instead the backups of your team's destroyed ClickHouse databases, with the name each came from; you can restore them into another one until they expire.

deplane clickhouse backup

Back up a ClickHouse database now. At most once an hour; it keeps running. At most 2 backups are kept, and this one counts.

deplane clickhouse restore

Replace a ClickHouse database's data with a backup: deplane clickhouse restore NAME BACKUP-ID. The backup can be from any ClickHouse database of your team, including a destroyed one; another one's backup brings its password, so the apps get a new CLICKHOUSE_URL and restart. Data written since the backup is lost, and the database stops while the restore runs. Asks for confirmation; pass --yes in scripts. It also needs your passkey in the browser each time; CI tokens and AI tools cannot restore.

deplane clickhouse destroy

Destroy a ClickHouse database and its data; its apps lose CLICKHOUSE_URL. Asks for confirmation; pass --yes in scripts. Needs your passkey each time. Its backups are kept for 3 days at most; deplane clickhouse backups --destroyed lists them.

There is no command to open a session with a ClickHouse database from your computer: it has no public address. To run SQL, use deplane run in an app whose image has a client; see ClickHouse.

Admin commands

These commands work only for Deplane administrators. Everyone else gets not_found. Each one needs a fresh passkey confirmation from the last 5 minutes; in a terminal the CLI opens the browser for it.

deplane admin requests

List beta access requests.

FlagWhat it does
--status STATUSpending (default), approved, or denied.
$ deplane admin requests
ID                         EMAIL             NAME      COMPANY   REQUESTED
acr_3n8m2b5v9c1x7z4l6k0j   dev@example.com   Dana Lee  Example   2026-10-12
{
  "access_requests": [
    {
      "id": "acr_3n8m2b5v9c1x7z4l6k0j",
      "email": "dev@example.com",
      "name": "Dana Lee",
      "company": "Example",
      "use_case": "Internal tools",
      "status": "pending",
      "created_at": "2026-10-12T16:20:00Z"
    }
  ]
}

deplane admin approve

Approve a pending access request by email or ID: deplane admin approve EMAIL|ID. This creates the user and a personal organization on the default plan, and tries to email them a sign-in link. Email sending is not set up yet, so the email may not arrive; contact them another way.

$ deplane admin approve dev@example.com
Approved dev@example.com. They were emailed a sign-in link.
{
  "user_id": "usr_6f1d9s4a8p2o7i3u5y0t",
  "org_id": "org_9r4e7w2q5t8y1u6i3o0p",
  "email": "dev@example.com"
}

deplane admin deny

Deny a pending access request by email or ID: deplane admin deny EMAIL|ID. Asks for confirmation; pass --yes to skip it.

$ deplane admin deny dev@example.com --yes
Denied dev@example.com.
{
  "denied": "acr_3n8m2b5v9c1x7z4l6k0j"
}

deplane admin abuse list

List abuse reports, oldest first, so the queue is worked in order.

FlagWhat it does
--status STATUSopen (default), investigating, actioned, rejected, or all.
$ deplane admin abuse list
ID                            STATUS  CATEGORY  APP            URL                                  REPORTED
abr_2k7j4h9g1f6d3s8a5p0o1q2w  open    phishing  pay-verify-01  https://pay-verify-01.deplane.app/x  2026-10-13 07:55
{
  "abuse_reports": [
    {
      "id": "abr_2k7j4h9g1f6d3s8a5p0o1q2w",
      "status": "open",
      "category": "phishing",
      "url": "https://pay-verify-01.deplane.app/x",
      "details": "This page copies our bank's login form.",
      "reporter_email": "victim@example.org",
      "reporter_ip": "203.0.113.9",
      "app_id": "app_8d2k4j6h1g3f5s7a9p0o1q2w",
      "app": "pay-verify-01",
      "org_id": "org_3j5h7g9f1d2s4a6p8o0i1u2y",
      "org_slug": "7f3k2p",
      "actions": [],
      "created_at": "2026-10-13T07:55:00Z",
      "resolved_at": null,
      "resolution_note": ""
    }
  ]
}

app, app_id, org_id, and org_slug are empty when the reported address is not a Deplane app. reporter_email is empty for an anonymous report.

deplane admin abuse show

Show one abuse report in full: deplane admin abuse show ID. This includes the details, the reporter, the reported app and its organization, and the history of the report.

$ deplane admin abuse show abr_2k7j4h9g1f6d3s8a5p0o1q2w
Report:    abr_2k7j4h9g1f6d3s8a5p0o1q2w
Status:    open
Category:  phishing
URL:       https://pay-verify-01.deplane.app/x
App:       pay-verify-01 (organization 7f3k2p, org_3j5h7g9f1d2s4a6p8o0i1u2y)
Reporter:  victim@example.org from 203.0.113.9
Received:  2026-10-13T07:55:00Z
Details:
  This page copies our bank's login form.

The JSON is one report, with the same fields as in deplane admin abuse list.

deplane admin abuse resolve

Move a report to a new status: deplane admin abuse resolve ID --status investigating|actioned|rejected [--note TEXT]. Each step is added to the report's actions history. actioned and rejected also record who resolved it, when, and the note. Resolving a report does not suspend anything; use suspend-app or suspend-org for that.

FlagWhat it does
--status STATUSRequired. investigating, actioned, or rejected.
--note TEXTWhat you found or did. Stored with the report.
$ deplane admin abuse resolve abr_2k7j4h9g1f6d3s8a5p0o1q2w --status actioned --note "Phishing confirmed; app suspended."
Report abr_2k7j4h9g1f6d3s8a5p0o1q2w is now actioned.

The JSON is the updated report, as in deplane admin abuse show, with status, resolved_at, resolution_note, and a new entry in actions (at, by, status, note).

deplane admin suspend-app

Suspend an app by name, in any organization: deplane admin suspend-app NAME --reason TEXT [--yes]. The app runs zero instances and can no longer be deployed or changed. Nothing is deleted. The reason is recorded in the organization's audit log. Without a terminal, pass --yes.

FlagWhat it does
--reason TEXTRequired, at most 500 characters. Why the app is suspended; include the report ID.
$ deplane admin suspend-app pay-verify-01 --reason "Phishing, report abr_2k7j4h9g1f6d3s8a5p0o1q2w" --yes
Suspended app pay-verify-01. Undo with `deplane admin unsuspend --app pay-verify-01`.
{
  "id": "sus_4h6j8k0l2z4x6c8v0b2n4m6q",
  "target_type": "app",
  "target_id": "app_8d2k4j6h1g3f5s7a9p0o1q2w",
  "reason": "Phishing, report abr_2k7j4h9g1f6d3s8a5p0o1q2w",
  "created_at": "2026-10-13T08:10:00Z",
  "lifted_at": null
}

deplane admin suspend-org

Suspend an organization by slug or ID: deplane admin suspend-org SLUG --reason TEXT [--yes]. All its apps run zero instances, and all its logins and tokens stop working (account_suspended). You cannot suspend your own organization.

FlagWhat it does
--reason TEXTRequired, at most 500 characters. Why the organization is suspended.
$ deplane admin suspend-org 7f3k2p --reason "Repeated phishing" --yes
Suspended organization 7f3k2p. Undo with `deplane admin unsuspend --org 7f3k2p`.

The JSON has the same shape as for suspend-app, with "target_type": "org" and the organization's ID as target_id.

deplane admin unsuspend

Lift a suspension: deplane admin unsuspend --app NAME or deplane admin unsuspend --org SLUG. Pass exactly one of the two. Apps return to the instance count they had. Without a terminal, pass --yes.

FlagWhat it does
--app NAMEUnsuspend this app.
--org SLUGUnsuspend this organization (slug or ID).
$ deplane admin unsuspend --org 7f3k2p --yes
Unsuspended organization 7f3k2p.

The JSON is the lifted suspension, with lifted_at set.

Help

deplane help prints the command list, the global flags, the environment variables, and the exit codes. deplane help --json prints:

{
  "commands": [
    {"command": "apps", "usage": "List your apps."},
    {"command": "deploy", "usage": "Build and deploy this folder's app again [--app NAME]."}
  ]
}

This page is also available as Markdown. AI agents can start at /llms.txt.