Deploying apps
Every way to ship an app on HostSSH — dashboard, CLI, git-push, and GitHub webhooks — plus builders, env, slots, volumes, workers, health gates, logs, and rollback.
Deploying apps
An App on HostSSH is one website, API, or worker. This guide covers how source becomes a running, HTTPS-served container, and every knob you can turn along the way.
Mental model: source → build → run → route. HostSSH builds your source into an image, runs it as a resource-capped container, and (if you gave it a domain) routes HTTPS to it — in one gated pipeline. If any stage fails its health check, the deploy rolls back and your previous version keeps serving.
1. Four ways to deploy
Pick the one that fits how you work. They all produce the same result — an App row in the control plane and a running container on a Node.
A. From the dashboard (no terminal)
Go to Deploy → New App. Fill in:
- Name — a stable identifier (
shop,api,blog). - Source — a Git URL, or a prebuilt image reference.
- Builder —
hostpack(auto),dockerfile, orimage(see §3). - Domain + Port — optional; the public hostname and the port your app listens on inside the container (see Domains & TLS).
- Node — which box runs it (the node selector).
- Slot size — optional caps (see §5).
- Environment — plain vars and sealed secrets (see Secrets & environment).
Click Deploy. A Job is queued, and you watch the live build log stream in the drawer.
B. From the CLI on the box
On a Node with the Agent installed:
$ hostssh deploy --name shop --source https://github.com/you/shop --ref main \
--builder hostpack --domain shop.example.com --port 3000 \
--readiness-path /healthz --slot rack1/3 --slot-size m \
--env NODE_ENV=production --env-file ./shop.env
Full flag reference is in CLI reference. The short version:
| Flag | Meaning |
|---|---|
--name | App name (required). |
--source | ./local/path or a Git URL. Defaults to . (current directory). |
--ref | Git branch, tag, or commit SHA. |
--builder | hostpack | dockerfile | image. Auto-detected if a Dockerfile is present. |
--domain --port | Public hostname + the container's listen port. |
--readiness-path | An HTTP path (e.g. /healthz) the deploy probes before declaring success. |
--command | Override the start command — marks the App a worker (no proxy, no port). |
--slot --slot-size | Place it in a Slot with caps (s/m/l/xl). |
--env K=V | A single env var (repeatable). |
--env-file f | Read KEY=VALUE lines from a file; --env overrides individual keys. |
--volume name:/path | A persistent named volume that survives redeploys (repeatable). |
--image-limit-mb | Fail the build if the final image exceeds this size (default 4096). |
C. Push from your laptop (hostssh push)
Like Fly.io's flyctl deploy. From your project directory:
$ hostssh push
It resolves the target App/Node from the control plane, streams your working tree to the Agent, and runs the same pipeline — no Git remote required. Good for iterating before you wire up automated deploys.
D. Git push / GitHub webhook (automated)
Two automated surfaces, both live:
- git-push — add HostSSH as a Git remote and
git pushto deploy (Dokku-style). The Agent's post-receive hook builds the pushed commit. - GitHub webhook — point a repo's webhook at
POST /api/v1/git/webhook. Every push to the configured branch triggers a build of that exact commit (Vercel-style). HostSSH clones by commit SHA, so the container always matches the commit that fired the hook.
Set these up in Deploy → App → Automated deploys. See Migrating to HostSSH for the full CI-style flow.
2. What happens during a deploy (the pipeline)
Every deploy runs the same gated sequence. Watching the log, you'll see stages like:
▸ source cloned github.com/you/shop @ a1b2c3d
▸ build hostpack detected: node → building image hostssh/shop:42
▸ tag hostssh/shop:42
▸ net ensured network hostssh
▸ migrate ran release command (if configured)
▸ run started container shop (slot=rack1/3 cpus=1.00 memMB=1024 pids=512)
▸ gate liveness ✓ · http /healthz → 200 ✓
▸ proxy routed shop.example.com → :3000
✓ cert issued by Let's Encrypt
✓ deployed shop → https://shop.example.com
- Source — clones the Git repo (by branch or exact SHA) into a temporary, agent-owned workspace, or uses your local folder.
- Build — turns source into an image via the chosen builder (§3).
- Run — starts the new container with your env, labels, caps, volumes, and a restart policy. The old container keeps running until the new one is proven.
- Gate — proves the new container actually works: it must be alive, and (if you set a
--readiness-path) it must answer HTTP on that path. An optional browser smoke test can render the page in a real browser to catch "up but blank/JS-crashed." - Route — stamps proxy labels so the managed Traefik picks up the domain and requests a certificate.
- Rollback on failure — if any gate fails, the deploy restores your previous version instead of leaving you down. A failed deploy is a non-event for your users.
Why this matters: a "green deploy" on HostSSH means proven serving, not "the process started." This is the discipline behind guards G1/G2/G8 (see Troubleshooting).
3. Builders — choosing how source becomes an image
hostpack (recommended default)
Zero-config. Point it at a repo and it detects the language and framework (Node, Next.js, static sites, and more) and builds a production image with sensible defaults. No Dockerfile needed. Under the hood it's our HostPack builder (Railpack-based) driving BuildKit.
Use it when: you want the simplest possible path and your app is a standard web app.
dockerfile
You commit a Dockerfile to your repo; the Agent runs docker build. Full control over the
runtime, system packages, and multi-stage builds.
Use it when: you need specific system dependencies, a non-standard runtime, or you already maintain a Dockerfile.
Build-time environment: if your build step reads environment variables (for example a
strict env:check that validates config at build time, common in Next.js), those values must
be in the build context, not just the runtime. HostSSH handles the runtime env for you; for
build-time env with a Dockerfile, pass --env/--env-file and make sure your .dockerignore
doesn't strip the env file. (This is guard G11; see Troubleshooting.)
image
You already have a built, pushed image; HostSSH just runs it. No build stage at all.
Use it when: your CI already builds images, or you're running an off-the-shelf image.
4. Web apps vs. workers
- A web app listens on a port and gets a domain and HTTPS. Give it
--port(and usually--domain). HostSSH auto-injectsHOSTNAME=0.0.0.0so frameworks like Next.js bind correctly, adds a health check, and routes traffic to it. - A worker has no port and no domain — a queue consumer, a cron runner, a background
processor. Mark it a worker by overriding the start command with
--command 'pnpm worker'. Workers get the same env, caps, and restart policy, but no proxy route.
Migrating a stack: if you're moving an app that has both a web process and background workers, deploy them as separate Apps — one web, one (or more) workers. During a migration, stop the old workers before cutting over so you don't double-process jobs.
5. Slots — capping what an App can use
Deploying --slot rack1/3 --slot-size m binds the App to Slot #3 on that Node's Rack with the
m caps (1 vCPU / 1 GB / 512 PIDs). This makes the App a real isolation unit — it can't starve
its neighbors, and the dashboard can meter and place it.
- Unslotted deploys (no
--slot-size) run with no caps. Fine for a box you own fully. - Slotted deploys are enforced by Docker and tracked on the Slot Board.
One workload per Slot. HostSSH refuses to double-book a Slot. Full details, sizing guidance, and the Slot Board are in Slots & capacity.
6. Persistent data — volumes
By default a redeploy replaces the container, so anything written to the container filesystem is lost. For state that must survive redeploys (an uploads directory, a SQLite file, WordPress files), attach a named volume:
$ hostssh deploy --name blog --source ./blog --volume blogdata:/var/www/uploads
- Only named volumes are allowed (
name:/container/path) — host bind mounts (/host:/c) are rejected for safety. - Named-volume data is included in backups and node-restore, so a rebuilt box brings your files back (not just the volume name).
For a stateful app, always confirm it has volumes before you rely on backups. See Backups & recovery.
7. Environment & secrets
Pass configuration two ways:
- Plain env (
NODE_ENV, feature flags, non-secret URLs) — stored in the control plane, injected into every deploy, visible in the dashboard. - Sealed secrets (
DATABASE_URL, API keys, tokens) — encrypted at rest, decrypted only inside the Agent, never shown in logs or the job queue.
Env lives on the App (the stable deployment row), so it survives redeploys. You can edit env after deploy in App Settings → Environment Variables, then click Redeploy to apply. Full details, including the security model, are in Secrets & environment.
8. Watching, stopping, and rolling back
- Live logs during deploy stream in the dashboard drawer and to the CLI. Every stage line is real — no fake progress bars.
- Runtime logs (after deploy) are available from the App page and via
hostssh logs <app>on the box. - Stop / remove an App from Deploy → App → … or
hostsshjob kindsstop/remove. Stopping keeps the App row and its env; removing deletes the container. - Redeploy re-runs the pipeline on the same App (same env, same Node pin) — the standard way to ship a new version or apply an env change.
- Cancel a running deploy from the Jobs view — the Agent tears down the in-progress build and leaves your previous version untouched.
- Rollback is automatic on a failed gate. To roll back a successful deploy that turned out bad, redeploy the previous ref/commit.
9. Guardrails you get for free
Because HostSSH learned these the hard way (the fix-forward "build guards"), every deploy already includes:
HOSTNAME=0.0.0.0injected for web apps so Next.js-style servers don't bind IPv6-only and 502 (G1).- A container health check + restart policy so a silently-dead listener gets restarted, not left 502-ing (G2).
- Build cache preserved across deploys so rebuilds are fast (G7).
- Rollback on any failed gate so a bad build never takes you down (G8).
- Persistent volumes so stateful apps keep their data across redeploys (G3/volumes).
You don't configure any of these — they're the platform defaults. The full catalog is in
Troubleshooting and, for developers, docs/dev/build-guards.md.
Next steps
- Put a real domain and automatic HTTPS on your App → Domains & TLS
- Hand it secrets safely → Secrets & environment
- Watch it and get paged if it breaks → Monitoring & alerts
- Move an existing app off Coolify/Vercel → Migrating to HostSSH