What you're setting up
JustDeploy itself is a zero-dependency Node program. The heavy lifting is done by Caddy (TLS + reverse proxy), Docker, and Railpack (container builds for Next.js and any framework). Here's the whole box:
Everything below runs on the server, as root. On a sudo user, prefix each
command with sudo. You'll need a VPS with root SSH access, a domain whose DNS you
control, and ports 80 and 443 open.
Point your DNS at the server
Do this first so Caddy can fetch a TLS certificate the moment an app exists. For each hostname
you'll deploy, add an A record to your VPS's public IP — a wildcard
*.example.com lets every app get its own subdomain automatically:
| Record | Type | Value |
|---|---|---|
*.example.com | A | <your-server-ip> |
panel.example.com | A | <your-server-ip> |
On Cloudflare, set these to DNS only (grey cloud), not proxied — Caddy handles TLS itself. A wildcard record means you never touch DNS again per app.
Install JustDeploy
One command on a fresh Ubuntu box. It installs Node (the one thing JustDeploy needs to run
itself), clones the repo, links the justdeploy CLI, then hands off to
justdeploy setup — which installs and wires up everything else.
Re-running is safe; each step checks first.
curl -fsSL https://raw.githubusercontent.com/codellyson/justdeploy/master/install.sh | bash
Piping a script to bash as root? Reasonable to read it first —
it's short and on GitHub.
Prefer to do it by hand? The manual path is below.
Manual alternative. JustDeploy uses Node's built-in
SQLite, so it needs Node ≥ 22.5. Install that, clone, and link — then Step 3
(setup) does Caddy, Docker and Railpack.
# Node 22 (NodeSource)
apt-get update && apt-get install -y ca-certificates curl gnupg git
curl -fsSL https://deb.nodesource.com/setup_22.x | bash -
apt-get install -y nodejs
# JustDeploy — pure Node stdlib, no npm deps for the engine
git clone https://github.com/codellyson/justdeploy /opt/justdeploy
cd /opt/justdeploy && npm link
State is created on first use at /var/lib/justdeploy/state.db; apps live under
/srv/<name>/. Override with JUSTDEPLOY_HOME and
JUSTDEPLOY_SRV if needed.
Ready the host auto
This is the part JustDeploy does for you. setup detects what's missing and installs
Caddy (TLS + reverse proxy, admin API on localhost:2019),
Docker, and Railpack + a BuildKit daemon for container builds, then
makes sure the Caddy service and admin API are live. The one-command installer above already ran it;
run it yourself after a manual install:
justdeploy setup # installs Caddy + Docker + Railpack, starts + verifies
justdeploy setup --no-docker # skip Docker (no Postgres, no container builds)
It's idempotent — anything already present is skipped, so it's safe to re-run. It runs as root (it installs system packages) and only supports Debian/Ubuntu; on anything else it prints the manual steps instead of guessing. When it finishes you'll see a green checklist:
→ Caddy already installed — skipping.
→ enabling + starting the Caddy service…
→ installing Railpack (container build tool)…
✓ Node 22.11.0
✓ Caddy installed
✓ Caddy service running
✓ Caddy admin API (http://localhost:2019)
✓ Docker
✓ Railpack (container builds)
✓ BuildKit daemon
Host ready. Deploy your first app:
justdeploy add https://github.com/you/site.git
Caddy installs as a root systemd service with the admin API enabled by default —
exactly what JustDeploy needs (root to bind :80/:443 and manage the DB firewall). JustDeploy owns
Caddy's live config through that API and rebuilds it from state.db, so you never touch
/etc/caddy/Caddyfile.
Deploy your first app
Point JustDeploy at a git repo — that's it. It detects the type from the repo's
package.json and infers the domain, so a bare add just works:
# type detected (Next/Adonis/Vite/CRA/static), domain inferred as .
justdeploy add https://github.com/you/site.git
# …or pin either explicitly
justdeploy add https://github.com/you/api.git --type adonis --domain api.example.com
Static apps (React/Vite/static) are served straight from Caddy; Next.js and the catch-all
app type build into a container with Railpack (it detects the package
manager and runtime — pnpm, Node version, and so on — automatically). add clones, builds,
wires up Caddy (which fetches a Let's Encrypt cert), and starts serving. Manage it:
justdeploy ls # what's deployed, ports, pids
justdeploy logs app -f # tail the build/run log
justdeploy deploy app # redeploy (pull → build → swap)
justdeploy rollback app # instantly revert to the previous release
justdeploy gc # reclaim disk: trim old container images
Supported --type values: vite, react, static,
adonis, nextjs, app, postgres.
Web dashboard optional
JustDeploy ships a control panel and deploys its own dashboard like any other app. One command builds the React UI, installs a systemd service, and adds the TLS route:
justdeploy dashboard install --domain panel.example.com
It prints an admin password — save it. The panel runs as the
justdeploy-dashboard systemd service on 127.0.0.1:4999, served over HTTPS by
Caddy. On first visit, a setup wizard walks you through host readiness, base domain,
GitHub, and your first deploy. Reset the password any time with
justdeploy dashboard password <new>.
Private repos optional
Public repos clone without auth. To deploy private repos, give JustDeploy a GitHub
personal access token (classic, repo scope — create one at
github.com/settings/tokens):
justdeploy github <your-token> # stores it; private repos will now clone
justdeploy github # show connection status
justdeploy github --clear # disconnect
In the dashboard you can paste the same token in the new-project flow to browse and pick private repos directly.
Git-push auto-deploy optional
Turn on a signed webhook so pushing to a repo's default branch redeploys the matching app automatically:
justdeploy webhook
It prints the Payload URL and secret to paste into GitHub → repo Settings → Webhooks
(content type application/json, "Just the push event"). Rotate the secret with
justdeploy webhook --rotate.
The webhook is received by the dashboard service, so this needs the dashboard installed (Step 5) to have a public URL.
Off-box backups recommended
state.db and each app's data/ dir are the only irreplaceable state on the
box — so back them up elsewhere. JustDeploy uploads to any S3-compatible bucket (AWS S3 or Cloudflare
R2), no extra dependencies:
justdeploy backup config --endpoint https://<acct>.r2.cloudflarestorage.com \
--bucket my-backups --access-key <k> --secret-key <s> --region auto
justdeploy backup # snapshot + upload (state.db + data + pg_dump)
justdeploy backup --schedule daily # optional: install a systemd timer
justdeploy restore <file> --yes # bring it all back
The backup archive is chmod 600 — it contains secrets (env vars, admin hash,
webhook secret). Keep it somewhere private.
Verify the install
One command reports everything — it installs nothing, just checks:
justdeploy doctor
✓ Node 22.11.0
✓ Caddy installed
✓ Caddy service running
✓ Caddy admin API (http://localhost:2019)
✓ Docker
✓ Railpack (container builds)
✓ BuildKit daemon
All green? Open your app's domain in a browser — it should load over HTTPS with a valid
certificate. Any red line tells you what to fix; re-run justdeploy setup to install
what's missing.
Firewall (ufw)? Allow OpenSSH, 80/tcp, and
443/tcp. Docker's published ports bypass ufw by design — JustDeploy binds Postgres to
127.0.0.1 only, and enforces a source-IP allowlist in the DOCKER-USER
chain when you deliberately make a database public. You don't manage those rules by hand.
Small VPS? Container builds of heavy apps can exhaust memory on boxes under
~2 GB RAM. Add a swap file — fallocate -l 4G /swapfile && chmod 600 /swapfile &&
mkswap /swapfile && swapon /swapfile — and add it to /etc/fstab.
Troubleshooting
| Symptom | Cause & fix |
|---|---|
cannot reach Caddy admin | Caddy isn't running or the admin API is off. systemctl status caddy; make sure no custom Caddyfile disabled the admin endpoint. |
| Cert error / domain won't resolve | DNS A record isn't pointing at the server yet, or is proxied (Cloudflare orange cloud). Set it to DNS-only and wait for propagation. |
node:sqlite errors on start | Node older than 22.5. Check node --version and reinstall from Step 2. |
| Container build fails / OOM | Heavy build ran out of RAM. Add a swap file (see Verify), or declare the Node version your app needs via .nvmrc / engines. |
| Private repo fails to clone | No GitHub token, or it lacks repo scope. Re-run justdeploy github <token>. |
| Deploy fails | The failure prints a plain-English reason and the fix — read it in justdeploy logs <app> or the dashboard's deploy log. |
Uninstall
Reverses setup. By default it removes everything — apps, databases,
Caddy (package + config), state, and the checkout — after printing the plan and prompting
y/N. Flags only hold things back:
justdeploy uninstall # full removal (prompts to confirm)
justdeploy uninstall --keep-data # keep state.db, app files, and db volumes
justdeploy uninstall --keep-caddy # leave Caddy installed, just drop the routes
justdeploy uninstall --yes # skip the prompt (scripts / non-interactive)
It always shows exactly what it will do and waits for y before touching anything, so
there's no way to nuke the box by fat-fingering one command. Docker is always left in place — it's a
shared tool. Piped/non-interactive runs (no terminal) need --yes instead of the prompt.
The default takes your databases with it. If there's anything you might want back, run
justdeploy backup first — or add --keep-data.