Quickstart
Go from a folder of code to a live HTTPS URL with deplane launch, then deploy, watch, scale, and roll back.
You need the CLI and a login. See Install.
Launch
Run deplane launch in your project's root folder:
$ cd my-shop
$ 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...
Deploying: 0 of 1 instances ready.
Running: 1 of 1 instances ready.
Deployed my-shop v1: https://my-shop.deplane.app
launch does all of this without asking you anything:
- It works out how to build the folder and prints one line about it.
- It creates an app named after the folder. If that name is taken, it adds a short random suffix, such as
my-shop-k3x9. To pick the name yourself, pass--name NAME. - It writes
deplane.toml. - It packs the folder and uploads it.
.gitignoreand.dockerignoreare honored. The.gitfolder and every.envfile are never uploaded. - It builds the app and streams the build log.
- It creates release v1, starts the new instance, waits until it is healthy, and prints the URL.
Every app gets an address of the form https://NAME.deplane.app with HTTPS already set up.
A first deploy takes one to two minutes. Measured end to end in October 2026: a static site 56 seconds, Express 1 minute 12 seconds, Go 1 minute 16 seconds, Flask 1 minute 17 seconds, FastAPI 1 minute 20 seconds, Next.js 2 minutes 3 seconds. Later deploys reuse the build cache and are faster.
What launch detects
If the folder has a Dockerfile, Deplane builds it. Otherwise it builds the project without any configuration. It recognizes these, in this order:
| Project | Recognized by | Frameworks it names | Default port |
|---|---|---|---|
| Dockerfile | Dockerfile | none | the first EXPOSE port, else 8080 |
| Node | package.json | Next.js, Nuxt, Remix, SvelteKit, NestJS, Express, Fastify, Koa, Hono (3000); Astro, Angular, Create React App, Gatsby, Vite (8080) | 3000 |
| Python | requirements.txt, pyproject.toml, Pipfile, setup.py, or uv.lock | Django, FastAPI, Flask | 8000 |
| Go | go.mod | Gin, Echo, Fiber, Chi | 8080 |
| Ruby | Gemfile | Rails, Sinatra | 3000 |
| PHP | composer.json or index.php | Laravel, Symfony | 8080 |
| Java | pom.xml, build.gradle, or build.gradle.kts | Spring Boot | 8080 |
| Static site | index.html or public/index.html | none | 8080 |
The "Detected" line shows what it found, for example:
Detected Next.js 15, Node 22, port 3000Detected Flask 3, Python 3.12, port 8000Detected Go 1.23, port 8080Detected a Dockerfile, port 8080Detected a static site, port 8080
Language versions come from files such as engines.node in package.json, .nvmrc, .python-version, go.mod, and .ruby-version.
If nothing matches, launch stops with detect_failed. Run it in your project's root folder, or add a Dockerfile.
Your app must listen on PORT
Deplane always sets the PORT environment variable to the app's port. Make your app listen on 0.0.0.0 at that port. Most frameworks do this already. If your app listens on localhost only, or on another port, the release fails with HealthCheckFailed.
deplane.toml
launch writes deplane.toml the first time. The file is optional, and every field has a working default. It looks like this:
# Deplane app settings. Every field is optional: https://deplane.io/docs/quickstart#deplanetoml
app = "my-shop"
[service]
port = 3000 # your app must listen on 0.0.0.0 at this port; it is also in $PORT
# health_check = "/healthz" # optional; a TCP check on the port if omitted
# release_command = "npm run migrate" # optional; runs before each new version serves
All the fields:
| Field | Default | Meaning |
|---|---|---|
app | the folder name | The app this folder deploys to. |
port (under [service]) | the detected port, else 8080 | The port your app listens on. |
instances (under [service]) | 1 | How many instances run. |
size (under [service]) | small | small, medium, or large. See Limits. |
health_check (under [service]) | none: a TCP check on the port | A path such as /healthz that must answer 200. |
release_command (under [service]) | none | A command that runs once before each new version starts serving, usually a database migration. See Release command. |
Each deplane deploy applies the file. If you set instances or size here, each deploy sets the app back to these values. Leave them out if you prefer to change them with deplane scale. Unknown fields and bad values stop the command with invalid_config.
Commit deplane.toml with your code. Commands run in this folder use its app. Anywhere else, pass --app NAME.
Deploy changes
After you change your code, run:
$ 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...
Deploying: 0 of 1 instances ready.
Running: 1 of 1 instances ready.
Deployed my-shop v2: https://my-shop.deplane.app
If the new release does not become healthy, it is marked failed and the previous release keeps serving. The command exits with code 4 and tells you why and what to do. See Troubleshooting.
Deploy a Docker image
If you already have an image, deploy it without source code:
$ 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
--name chooses the app's name (default: the image's name), and --port the port the image listens on (default 8080). To put a new image on an app you already have, run deplane deploy --image REF --app NAME. The dashboard does the same under New app, Docker image. An image app has no folder, so pass --app NAME to later commands. --image cannot be combined with a folder, a path or --template.
- Public images only. Docker Hub, GitHub's registry (
ghcr.io) and other public registries work. Private registries do not (see private_registry_unsupported); deploy your code instead. - Pin a version.
deplane redeploypulls the same tag again. A reference ending in@sha256:and 64 hex characters always gives the same image. - Your app runs as user 10001. Deplane runs every app as that user, whatever the image says: never as root, with no extra privileges, and on ports above 1024. An image that needs root, or that writes to folders owned by another user, fails to start. Use images made to run as a regular user, and have the app listen on
$PORTor the port you set.
Images made to run as a regular user, such as nginxinc/nginx-unprivileged (port 8080), are the ones to start from. For your own app, build an image that does not need root and push it to a public registry. These two are examples of that shape, one for Go and one for Node:
FROM golang:1.23 AS build
WORKDIR /src
COPY . .
RUN CGO_ENABLED=0 go build -o /app .
FROM gcr.io/distroless/static
COPY --from=build /app /app
ENTRYPOINT ["/app"]
FROM node:22-slim
WORKDIR /app
COPY --chown=10001:10001 . .
RUN npm ci --omit=dev
ENV PORT=8080
CMD ["node", "server.js"]
If your code is in a folder, deplane launch builds it for you and you need none of this. Image deploys use build minutes like any build, and run the release command if the app has one.
Run a command once
deplane run runs one command in your app's live release and shows its output, for a seed script, a backfill, or any one-off task:
$ deplane run -- python manage.py createcachetable
Started run run_4m8q2k7x9c1v5b3n6z0a in my-shop (release v3).
Cache table created.
$ echo $?
0
Everything after -- is the command. Its words are passed as you typed them, spaces and quotes included. For a pipe or &&, run a shell: deplane run -- sh -c "python manage.py migrate && python manage.py seed". The command's exit code is the exit code of deplane run.
- It runs in the release that was live when it started, with the app's variables and database access, and no disk. It does not take traffic. Add
--size mediumfor more memory, up to your plan's largest size (defaultsmall), and--no-waitto return at once. - The output shows while the run exists, and for an hour after it ends. It is not archived. With
--no-wait, follow it withdeplane logs -f --process run_ID. - There is no interactive shell and no input. A command that waits for you to type does not work. Press Ctrl-C to cancel the run.
- Limits. A run lasts at most an hour, after waiting up to an hour for a free slot. An app can have 2 runs at once and a team 5, and a team can start 30 runs an hour. A team's runs and release commands share 2 slots at once, see run_busy.
- Who. You and CI tokens. AI tools cannot run commands.
See deplane run.
Check status
$ deplane status
App: my-shop (active)
URL: https://my-shop.deplane.app
Release: v2
Instances: 1 of 1 ready, size small
State: Running
Last build: bld_9t2w6r1y8u3i5o7p4a0s, succeeded
When something is wrong, status adds a Problem: line and a Fix: line.
Read logs
$ deplane logs
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. deplane logs shows the last 100 lines of each running instance. Use --tail N for up to 1000 lines. Follow new lines live with -f, and stop with Ctrl-C:
deplane logs -f
Open the app
deplane open
This opens the URL in your browser and prints it.
Scale
Run more instances, change the size, or both:
$ deplane scale 2 --size medium
my-shop now runs 2 medium instance(s).
deplane scale 0 stops the app without deleting it. Your plan sets how many instances and how much memory you can use. See Limits.
Let Deplane add instances under load, or put the app to sleep while nobody uses it:
$ deplane scale 1 --max 3 --sleep 15m
See Scaling.
Roll back
List releases:
$ deplane releases
VERSION STATUS IMAGE CREATED DESCRIPTION
v2 active 0b9e7d61a2c3 2026-10-13 09:41 Deploy
v1 superseded 4f1c2a9e8b7d 2026-10-13 09:12 Deploy
Roll back to the previous release that ran successfully, or to a version you choose:
$ deplane rollback
Roll back my-shop to the previous good release? [y/N] y
Created release v3 (Rollback to v1). Run `deplane status` to follow the rollout.
A rollback creates a new release with the earlier release's build and settings. Nothing is deleted.
If your app starts returning 5xx errors in a release's first 10 minutes live, Deplane rolls it back by itself and tells your team. See Automatic rollback; turn it off with deplane config --auto-rollback off.
Destroy
$ deplane destroy
Destroy app my-shop? Its instances, releases, and secrets are deleted, and the name becomes available to others. [y/N] y
Destroyed my-shop.
This cannot be undone.
Next steps
- Add configuration and API keys with Secrets.
- Run your migrations before each release with a release command, and let your apps talk to each other over a private network.
- Learn every command in the CLI reference.
- Deploy on every push, with a preview for every pull request: GitHub.
- Add a Postgres database, backed up continuously: Databases.
- Deploy from CI with Agents, CI, and scripts.
- Let Claude, ChatGPT or Cursor deploy for you: AI tools (MCP).
This page is also available as Markdown. AI agents can start at /llms.txt.