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 self-hosted platform-as-a-service — your own Heroku or Vercel, running on servers you control instead of someone else’s cloud. 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 Traefik 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 the entire platform — dashboard, deploy worker, database and proxy — runs on a single Linux box with Docker.
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. Attach custom domains any time, and scale it out with one click.
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.
$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.jsDeployCloud 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 singlewebprocess from the image’sCMD.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 ofwebbut 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 workerRuby / Rails
web: bundle exec puma
release: bundle exec rake db:migrate
worker: bundle exec sidekiqweb 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_KEYandS3_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.
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. 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.
Outgrowing one box? In multi-node mode the platform becomes a small Docker Swarm fleet: apps run as replicated services scheduled across many worker nodes (added by hand or provisioned on demand), all behind a single Traefik ingress. Your app compute scales out while the dashboard, database and proxy stay on the manager.
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. 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 type —
application/json(form-encoded payloads sign differently). - Secret — the app’s deploy token, shown on its Settings page.
- 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.
shipdeck binary. Until it’s renamed, run shipdeck wherever these docs write deploycloud — e.g. shipdeck 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 visibleThen 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 deploydeploy --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:
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"}'Security model
DeployCloud is a single-trusted-team tool, not a multi-tenant SaaS. The most important thing to understand before you run it: anyone who can deploy an app effectively has root on the host. The worker builds and runs containers through the host’s Docker daemon, and a deploy is arbitrary code — there’s no sandbox between “can push a deploy” and “owns the box”. Treat dashboard access exactly like SSH access, and give it only to people you’d hand a root shell.
Practical consequences:
- Keep signups closed. In production, registration is off by default — only the very first signup succeeds, and it claims the instance. Register the owner account immediately after first boot so no one else can.
- Keep the dashboard off the public internet. It’s an internal operations console — put it behind a VPN or an IP allowlist rather than exposing it openly. (Deployed apps are meant to be public; the dashboard is not.)
- Secrets are encrypted at rest. App env vars and add-on credentials are encrypted with AES-256-GCM under a secret key that only you hold. Generate it with real entropy and back it up separately from the database — losing it means losing every encrypted value, with no recovery.
- One team, one instance. There are no roles or per-app boundaries — everyone with access shares full control of every app. Run separate instances for teams that shouldn’t share root.
© 2026 DeployCloud · Self-hosted · open source
← Back to home