Documentation

Deploy, configure and scale on DeployCloud

Everything you need to take an app from a Git repository to a live, health-checked HTTPS URL on infrastructure you own.

On this page

Introduction

DeployCloud is a managed platform-as-a-service — your own managed cloud, fully hosted, with nothing to provision or maintain. You point it at a Git repository and it clones the code, builds an image (from your Dockerfile, or automatically when there isn’t one), and releases it behind a managed reverse proxy.

A new release only takes traffic once it passes a health check, so every deploy is zero-downtime and a broken build can never take your site down. Each app is served on its own subdomain with automatic HTTPS — and DeployCloud runs, scales and patches the underlying infrastructure so you never touch a server.

Getting started

Create an app. In the dashboard choose New App, give it a name and paste the URL of the Git repository you want to deploy, then pick the branch to track (defaults to main). That’s the whole setup — there’s nothing to install in your repo.

Deploy it. Trigger the first build from the dashboard’s Deploy button, from the CLI, or by POSTing to the app’s deploy hook — handy from CI:

curl -X POST https://deploycloud.example.com/api/hooks/deploy/<deploy-token>

DeployCloud clones the branch, builds the image, runs any release step, and rolls the new version in behind the proxy once it’s healthy.

It goes live. Your app is served at <your-app>.<your-domain> with a Let’s Encrypt certificate issued automatically — for example myapp.deploycloud.example.com. Custom domains become active after their DNS TXT ownership challenge verifies; then deploy once to publish the route.

How builds work

DeployCloud builds your repository one of two ways, and it decides for you based on what’s in the build root:

  • Dockerfile present. If your repo has a Dockerfile, it’s built exactly as written — you have full control over the image.
  • No Dockerfile. Otherwise the language is auto-detected and an image is produced for you with Nixpacks — no configuration required. Node, Python, Go, Ruby, PHP, Rust, Java and more are supported out of the box.
Your web process must listen on the port given in the $PORT environment variable, not a hardcoded one. DeployCloud injects PORT and health-checks exactly that port — binding elsewhere means the health check never passes and the deploy rolls back.

Procfile & processes

Most real apps are more than one process: a web server, a background worker, and a migration step that has to run before the new code goes live. A Procfile at your build root declares all three, one process per line, in the same format across every language:

web: node server.js
release: npx prisma migrate deploy
worker: node worker.js

DeployCloud recognises exactly three behaviours, keyed off the process name:

  • web — the one routed, health-checked process that gets a URL. It’s what your replica count scales, and it must listen on $PORT. A repo with no Procfile still gets a single web process from the image’s CMD.
  • release — a one-shot command run to completion before traffic shifts, against the freshly built image with real env vars. This is where database migrations go. A non-zero exit aborts the deploy — the previous release keeps serving, so a broken migration can’t take you down. Its output streams into the deploy logs.
  • Any other name (worker, clock, scheduler…) — a long-running background worker. It shares the image and env of web but is not routed and not health-checked, and is restarted if it crashes.

The format is identical everywhere; only the commands change:

Python / Django

web: gunicorn app.wsgi
release: python manage.py migrate
worker: celery -A app worker

Ruby / Rails

web: bundle exec puma
release: bundle exec rake db:migrate
worker: bundle exec sidekiq
Background workers run a single instance and are single-node only — in multi-node mode only web is scheduled across the fleet. For a migration you want to run by hand, use a one-off command: deploycloud run <app> "npx prisma migrate deploy".

Environment variables

Set environment variables per app from Settings, or from the CLI and REST API. They’re encrypted at rest with AES-256-GCM and injected into your containers at deploy time — the platform stores only ciphertext, and listing shows keys, never values.

Changes apply on the next deploy. The environment is baked in when containers start, so after adding or changing a variable, redeploy to pick it up. Add-on credentials (below) are injected the same way, so DATABASE_URL and friends are always present.

$ deploycloud env myapp LOG_LEVEL=debug
Set LOG_LEVEL on myapp — redeploy to apply.

Add-ons

Provision a backing service in one click and DeployCloud generates its credentials and injects the connection details straight into your app’s environment. Three add-ons are built in:

  • Postgres — a dedicated database, exposed as DATABASE_URL.
  • Redis — an in-memory cache / queue, exposed as REDIS_URL.
  • MinIO — S3-compatible object storage, exposed as S3_ENDPOINT, S3_ACCESS_KEY and S3_SECRET_KEY.

Each add-on runs on the platform’s private network and is reachable directly from your app. Like any other configuration change, provisioning an add-on takes effect on the next deploy — so redeploy to apply.

Persistent volumes

By default an app’s filesystem is disposable: every deploy starts a fresh container from a fresh image, so anything written to disk at runtime is gone. A persistent volume is a named disk mounted into your containers at a path you choose that survives deploys — reach for one when your app owns files that must outlive a release:

  • a SQLite database file,
  • user uploads (images, attachments) written to the local filesystem,
  • an on-disk cache or index you don’t want to rebuild every release.

Add one under Settings → Persistent volumes. Give it a name (a short lowercase identifier, e.g. data) and an absolute mount path (e.g. /data); core system directories are rejected. Then point your app at it — for a SQLite app, mount data at /data and set DATABASE_URL=file:/data/app.db. The same volume is mounted into both your web and worker containers, and changes apply on the next deploy.

Or from the CLI, so a stateful app can be shipped end to end from a script or an agent:

$ deploycloud volumes add myapp data /data
ok volume data at /data — mounted on the next deploy

$ deploycloud volumes ls myapp
data /data

The same surface is on the REST API (GET/POST/DELETE /api/v1/apps/<slug>/volumes) and the MCP server (list_volumes, add_volume, remove_volume).

Volumes are single-node only and shared by every web replica on the host — fine for read-mostly data, but for write-heavy state (like SQLite under concurrent writes) run a single replica or use the Postgres add-on instead. Deleting a volume destroys its data with no undo.

Preview deployments

Turn on branch previews in an app’s settings and every branch you push gets its own live environment — built with the same pipeline as production, on its own subdomain:

<app>-pv-<branch>.<your-domain>

A preview runs in isolated containers with a single replica, so it can never touch production — your main deployment keeps serving, untouched. Share the URL on the pull request, click around the real app, then merge with confidence. When you delete the branch, its preview is torn down automatically — as is any preview that goes a week without a new push, and every preview of an app when you turn previews off. Push again and it comes right back. Previews are driven by the same GitHub webhook you set up for auto-deploys (below).

Scaling

Scale the web process horizontally by setting its replica count — from the dashboard, deploycloud scale <app> <n>, or the API. Scaling reuses the current release image (no rebuild) and rolls the change out with health checks. Traefik load-balances requests across all healthy replicas, so more replicas means more throughput with no changes to your app.

Need more throughput than a single instance can handle? Your app scales out across DeployCloud’s fleet automatically — replicas are scheduled across many nodes behind a single load-balanced ingress, with no infrastructure for you to add or manage. Turn on autoscaling and replicas track CPU up and down as traffic changes.

Deploy from GitHub

Wire up a GitHub webhook and a push to your tracked branch builds and releases automatically. The webhook is signed (HMAC-SHA256), so the signature is the authentication — no token ever appears in the URL. Ask the platform for the exact values to paste:

$ deploycloud github-webhook myapp
Payload URL: https://deploycloud.example.com/api/hooks/github/42
Content type: application/json
Secret: dc_whs_…
Events: push
Branch: main

The same values are on the app’s Settings → Deploys page and behind GET /api/v1/apps/<slug>/github-webhook. In your repository, open Settings → Webhooks → Add webhook and fill in:

  • Payload URL https://<your-platform-host>/api/hooks/github/<appId>, where <appId> is the numeric id in the app’s dashboard URL.
  • Content typeapplication/json (form-encoded payloads sign differently).
  • Secret — the app’s webhook secret from the command above. It is not the deploy token in the CI hook URL; pasting that one gets you 401 bad signature.
  • Events — “Just the push event”.

Save, and GitHub sends a ping — a green check with {"ok":"pong"} means the URL and secret are correct. Only pushes to the branch the app tracks enqueue a deploy; pushes to other branches, tags and branch deletions are acknowledged and ignored so the hook stays healthy. Enable branch previews (see above) and those other-branch pushes instead build a preview environment.

CLI

The command-line tool is a single zero-dependency Node script (Node 20+) that talks to the REST API with a bearer token. It covers the daily loop — deploy, watch, tail logs, manage env, scale, and run one-off commands — without leaving your terminal.

The CLI currently ships as the deploycloud binary. Until it’s renamed, run deploycloud wherever these docs write deploycloud — e.g. deploycloud deploy myapp --watch.

Log in by pointing the CLI at your platform and pasting an API token (created in the dashboard under Tokens, shown once). The token is verified before it’s saved:

$ deploycloud login https://deploycloud.example.com
API token (dashboard → Tokens): ••••••••
ok logged in — 3 apps visible

Then the everyday commands:

# list apps with status, scale and URL
$ deploycloud apps

# deploy and stream build logs; exits on success/failure
$ deploycloud deploy myapp --watch

# tail the runtime logs
$ deploycloud logs myapp --tail 100

# manage environment variables
$ deploycloud env myapp
$ deploycloud env myapp LOG_LEVEL=debug
$ deploycloud env myapp --unset LOG_LEVEL

# scale the web process
$ deploycloud scale myapp 3

# run a one-off command in the live release env
$ deploycloud run myapp npx prisma migrate deploy

deploy --watch polls until the deployment reaches running (exit 0) or failed (exit 1), which makes it drop-in for CI. run is synchronous and exits with the wrapped command’s own exit code, so it composes in scripts too.

REST API

Everything the CLI does is a plain REST API you can script directly. Every request is authenticated with a bearer token — create one in the dashboard under Tokens (shown once) and send it in the Authorization header:

Authorization: Bearer <token>

Apps are addressed by slug. The full surface:

GET/api/v1/apps
List every app with its latest deployment, scale and live URL.
POST/api/v1/apps
Create an app. Body: { "name": "...", "repoUrl": "...", "branch": "main" }.
GET/api/v1/apps/<slug>
Fetch one app, including its custom domains and environment variable keys.
DELETE/api/v1/apps/<slug>
Delete an app, its add-ons and their data. Irreversible.
POST/api/v1/apps/<slug>/deploy
Queue a build + release of the tracked branch. Idempotent while one is already queued.
GET/api/v1/apps/<slug>/deployments
The 20 most recent deployments for the app.
GET/api/v1/apps/<slug>/deployments/<id>
A single deployment with its full build and release log.
POST/api/v1/apps/<slug>/rollback
Re-release a previous deployment — no rebuild. Body: { "deploymentId": 41 } (default: the last one).
POST/api/v1/apps/<slug>/restart
Zero-downtime restart from the current image.
GET/api/v1/apps/<slug>/logs
Runtime container logs of the live release. Add ?tail=N to limit lines.
GET/api/v1/apps/<slug>/metrics
Recent CPU / memory / replica samples. Add ?since=SECONDS.
GET/api/v1/apps/<slug>/alerts
Recent down / CPU / memory alert events.
GET/api/v1/apps/<slug>/env
List environment variable keys. Values never leave the platform.
PUT/api/v1/apps/<slug>/env
Set a variable, encrypted at rest. Body: { "key": "...", "value": "..." }.
DELETE/api/v1/apps/<slug>/env
Remove a variable. Body: { "key": "..." }.
POST/api/v1/apps/<slug>/scale
Set the web replica count. Body: { "scale": 3 }.
POST/api/v1/apps/<slug>/run
Run a one-off command in the release image. Returns exit code + output.
POST/api/v1/apps/<slug>/maintenance
Serve the 503 page on the app’s hostnames, or route back. Body: { "enabled": true }.
POST/api/v1/apps/<slug>/previews
Turn branch-preview auto-deploys on or off. Body: { "enabled": true }.
GET/api/v1/apps/<slug>/domains
List custom domains with ownership challenge, verification and certificate status.
POST/api/v1/apps/<slug>/domains
Create a pending domain and TXT challenge. Body: { "hostname": "www.example.com" }.
PATCH/api/v1/apps/<slug>/domains
Verify the pending DNS TXT challenge. Body: { "hostname": "www.example.com" }.
DELETE/api/v1/apps/<slug>/domains
Detach a custom domain. Pass ?hostname=…
GET/api/v1/apps/<slug>/addons
List provisioned add-ons.
POST/api/v1/apps/<slug>/addons
Provision postgres, redis or minio. Body: { "type": "postgres" }.
DELETE/api/v1/apps/<slug>/addons
Destroy an add-on and its data. Pass ?type=…
GET/api/v1/apps/<slug>/volumes
List persistent volumes — the only paths that survive a deploy.
POST/api/v1/apps/<slug>/volumes
Mount one. Body: { "name": "data", "mountPath": "/data" }.
DELETE/api/v1/apps/<slug>/volumes
Detach a volume and delete its data. Pass ?name=…
GET/api/v1/apps/<slug>/crons
List scheduled jobs with their last-run status.
POST/api/v1/apps/<slug>/crons
Add a job. Body: { "name": "...", "schedule": "0 3 * * *", "command": "..." } (UTC).
DELETE/api/v1/apps/<slug>/crons
Remove a job. Pass ?id=…
GET/api/v1/apps/<slug>/github-webhook
The payload URL and HMAC secret to wire push-to-deploy in GitHub.
GET/api/v1/platform/domains
Custom domains for the platform itself. Admin only.
POST/api/v1/platform/domains
Attach a platform domain — applies live, no redeploy. Body: { "hostname": "..." }.
DELETE/api/v1/platform/domains
Detach a platform domain. Pass ?hostname=…

For example, to trigger a deploy and then set an environment variable:

# queue a deploy of the tracked branch  ->  { "deploymentId": 42, "queued": "ok" }
curl -X POST https://deploycloud.example.com/api/v1/apps/myapp/deploy \
  -H "Authorization: Bearer $DEPLOYCLOUD_TOKEN"

# set an environment variable (encrypted at rest)
curl -X PUT https://deploycloud.example.com/api/v1/apps/myapp/env \
  -H "Authorization: Bearer $DEPLOYCLOUD_TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"key":"LOG_LEVEL","value":"debug"}'

MCP server

Let an AI assistant operate DeployCloud for you. The MCP server exposes the platform over the Model Context Protocol, so a client like Claude can list apps, deploy and watch a release, tail logs, set env vars, scale, run one-off commands, and manage add-ons, domains and cron jobs — the same surface as the CLI and REST API. Like the CLI it’s a single zero-dependency Node script that authenticates with a bearer token, so a token is all it needs.

Point your client at it. Create an API token in the dashboard under Tokens (shown once), then add a server entry — for Claude Desktop, in claude_desktop_config.json:

{
  "mcpServers": {
    "deploycloud": {
      "command": "deploycloud-mcp",
      "env": {
        "DEPLOYCLOUD_URL": "https://deploycloud.example.com",
        "DEPLOYCLOUD_TOKEN": "dc_your_token_here"
      }
    }
  }
}

Any MCP client works the same way — in Claude Code it’s one command: claude mcp add deploycloud -e DEPLOYCLOUD_URL=… -e DEPLOYCLOUD_TOKEN=… -- deploycloud-mcp. Already logged in with the CLI? The server falls back to your deploycloud login config, so you can drop the env block entirely. Restart the client and it discovers all thirty-four tools, one per API v1 operation:

  • Inspectlist_apps, get_app, list_deployments, get_deployment, get_logs, list_env, get_metrics, list_domains, list_addons, list_volumes, list_crons, list_alerts, get_github_webhook.
  • Deploy & releasedeploy_app, upload_bundle, rollback_app, restart_app, scale_app, run_command.
  • Configureset_env / unset_env, set_maintenance, set_previews, add_domain / verify_domain / remove_domain, add_addon / remove_addon, add_volume / remove_volume, add_cron / remove_cron.
  • Lifecyclecreate_app and delete_app.

Deploys are asynchronous: deploy_app returns a deployment id, and the agent polls get_deployment until it reaches running or failed — the full build log streams back with every poll. run_command is synchronous and returns the command’s exit code and output.

An API token carries the same power as the dashboard — anything connected with it can deploy, run commands and delete apps. Treat it like an SSH key: only connect assistants you trust, and revoke the token in Tokens if it leaks.

Security

DeployCloud is fully managed, so the infrastructure your app runs on — the hosts, the container runtime, the proxy and the database — is operated, hardened and patched for you. Here is what that means for your apps and data:

  • Secrets encrypted at rest. App environment variables and add-on credentials are encrypted with AES-256-GCM. They are decrypted only to inject into your app at runtime, and are never shown in build logs, deploy output or the dashboard once saved.
  • Automatic TLS everywhere. Every app subdomain and custom domain gets a Let’s Encrypt certificate, issued and renewed automatically — HTTPS is on by default with no configuration.
  • Isolated per app. Each app runs in its own containers with its own environment, add-ons and volumes. Your account’s apps, API tokens and data are scoped to your account.
  • Managed backups. Schedule automatic Postgres backups with retention, download any dump, and restore one with a single click. A restore replaces the database — the schema is dropped and rebuilt from the dump, so anything written since it was taken is gone. A backup that fails is reported, never recorded as if it had worked.
You never manage servers, TLS or the reverse proxy — DeployCloud runs and secures the platform so you can focus on shipping your app.

© 2026 DeployCloud · Managed hosting · deploy any language

← Back to home