Normal install
local userbun run harbor:install
harbor self doctor
A practical guide for installing Harbor, registering endpoints, adopting workspaces, deploying apps, checking runtime state, and recovering when something goes sideways.
deploy --unit app --endpoint meowdeploy force --unit appdeploy --unit app --dry-runharbor, harbor unit listHarbor is a standalone CLI. The core path is local context plus explicit endpoint state.
| Entity | Meaning | Where to look |
|---|---|---|
| Workspace | Repo or monorepo root | harbor.config.jsonc |
| Unit | Deployable app/package | harbor unit list |
| Service | Runtime process/static site | harbor service list |
| Endpoint | Named SSH/runtime host | harbor endpoint |
| Release | One deployed snapshot | harbor deploy history |
Install the CLI launchers, including the short deploy binary.
bun run harbor:install
harbor self doctor
bun run harbor:install:force
exec zsh
harbor self install-completions
exec zsh
harbor complete -- deploy run --unit ""
An endpoint is the named SSH/runtime host Harbor deploys to.
harbor endpoint add meow meow --user deploy --identity-file ~/.ssh/id_ed25519
harbor endpoint set-default meow
harbor endpoint sync-ssh-config
harbor endpoint doctor meow
harbor endpoint add meow meow
harbor endpoint set-default meow
ssh meow true
harbor endpoint doctor meow
Harbor-managed SSH config is kept between # >>> harbor endpoints >>> and # <<< harbor endpoints <<<.
Use this when you are in a monorepo and want Harbor to discover deployable apps.
harbor workspace inspect
harbor workspace adopt
harbor workspace adopt --write
harbor unit list
harbor unit inspect apm
A minimal Node-compatible Bun/TypeScript service that Harbor can build locally and run remotely with Node.
mkdir -p apps/my-app/src
cat > apps/my-app/package.json <<'JSON'
{
"name": "my-app",
"private": true,
"type": "module",
"scripts": {
"typecheck": "tsc --noEmit",
"build": "bun build src/server.ts --target=node --outdir=dist",
"start": "node dist/server.js"
},
"devDependencies": {
"@types/node": "^24.0.0"
}
}
JSON
cat > apps/my-app/src/server.ts <<'TS'
declare const process: { env: Record<string, string | undefined> };
export {};
const { createServer } = await import("node:http");
const host = process.env.HOST ?? "127.0.0.1";
const port = Number(process.env.PORT ?? "3000");
const html = "<!doctype html><h1>Hello from Harbor</h1>";
createServer((_req, res) => {
res.writeHead(200, { "content-type": "text/html; charset=utf-8" });
res.end(html);
}).listen(port, host, () => {
console.log(`listening on http://${host}:${port}`);
});
TS
bun run --cwd apps/my-app typecheck
bun run --cwd apps/my-app build
node apps/my-app/dist/server.js
harbor unit add my-app --path apps/my-app --preset app
harbor unit inspect my-app
deploy is a shortcut for Harbor's deploy flow, not a second runtime.
deploy --unit my-app --endpoint meow
harbor deploy run --unit my-app --endpoint meow
deploy --unit my-app --endpoint meow --dry-run
deploy to meow using my-app.example.de --unit my-app
Use this when changes are uncommitted or the app directory is untracked. It still runs preflight.
deploy force --unit my-app --endpoint meow
deploy force --unit my-app --endpoint meow --dry-run
--force means "package this local working tree deliberately". It does not skip typecheck, test, or build.
Manual deploy is the primary path. Hooks are explicit local automation, not a remote daemon.
harbor ci install --unit my-app --endpoint meow
harbor ci status
harbor ci logs
harbor ci trigger
HARBOR_HOOK_DEPLOY=0 git commit --allow-empty -m "skip harbor deploy"
Use Harbor for the high-level view; SSH remains the direct escape hatch when debugging a host.
harbor domain inspect my-app.example.de
harbor endpoint doctor meow
curl -I https://my-app.example.de/
harbor runtime logs --service my-app
ssh meow 'sudo journalctl -u harbor-my-app --no-pager -n 80'
ssh meow 'sudo nginx -t'
ssh meow 'sudo systemctl reload nginx'
harbor secret set cloudflare/default
harbor secret list
harbor secret test cloudflare/default
Harbor records local receipts and release metadata so rollback can be audited.
harbor deploy history
harbor deploy history --sync --write --unit my-app --endpoint meow
harbor deploy rollback --dry-run --unit my-app --endpoint meow
harbor deploy rollback --unit my-app --endpoint meow
harbor deploy rollback <release-id> --unit my-app --endpoint meow
harbor workspace inspect
harbor workspace adopt --write
harbor unit list
harbor unit list
deploy --unit <unit> --dry-run
deploy --unit <unit>
harbor endpoint
harbor endpoint inspect meow
ssh meow true
harbor endpoint doctor meow
deploy force --unit <unit> --endpoint meow --dry-run
deploy force --unit <unit> --endpoint meow
{
"services": {
"api": {
"listen": {
"type": "port",
"host": "127.0.0.1",
"port": "auto",
"range": "31000-31999"
},
"env": {
"PORT": "${harbor.port}",
"HOST": "${harbor.host}"
}
}
}
}
{
"units": {
"web": {
"preset": "static",
"path": "apps/web",
"build": { "cwd": "workspace", "command": "bun run --filter web build" },
"source": { "strategy": "artifact", "artifact": "apps/web/dist" },
"services": ["web"]
}
}
}
| Path | Purpose |
|---|---|
harbor.config.jsonc | workspace units/services/stacks |
.harbor/runs/ | local command receipts |
.harbor/releases.json | local release index |
~/.config/loom/harbor/config.json | endpoints and UI preferences |
~/.local/share/loom/harbor/registry.json | workspace/deploy registry |
/srv/harbor/apps/<name>/ | remote app release tree |
/etc/systemd/system/harbor-<service>.service | remote systemd service |