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.
  • Builderhostpack (auto), dockerfile, or image (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:

FlagMeaning
--nameApp name (required).
--source./local/path or a Git URL. Defaults to . (current directory).
--refGit branch, tag, or commit SHA.
--builderhostpack | dockerfile | image. Auto-detected if a Dockerfile is present.
--domain --portPublic hostname + the container's listen port.
--readiness-pathAn HTTP path (e.g. /healthz) the deploy probes before declaring success.
--commandOverride the start command — marks the App a worker (no proxy, no port).
--slot --slot-sizePlace it in a Slot with caps (s/m/l/xl).
--env K=VA single env var (repeatable).
--env-file fRead KEY=VALUE lines from a file; --env overrides individual keys.
--volume name:/pathA persistent named volume that survives redeploys (repeatable).
--image-limit-mbFail 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 push to 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
  1. Source — clones the Git repo (by branch or exact SHA) into a temporary, agent-owned workspace, or uses your local folder.
  2. Build — turns source into an image via the chosen builder (§3).
  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.
  4. 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."
  5. Route — stamps proxy labels so the managed Traefik picks up the domain and requests a certificate.
  6. 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

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-injects HOSTNAME=0.0.0.0 so 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 hostssh job kinds stop/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.0 injected 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