Skip to main content
The Harly CLI installs and operates a self-hosted Harly deployment. It generates the Docker Compose topology, creates independent runtime secrets, waits for service readiness, and provides safe day-two operations such as backups and upgrades.

What you will learn

  • Check a host before installing Harly.
  • Generate and launch a local Docker deployment.
  • Automate the installer from CI or an agent without unexpected prompts.
  • Provision Railway or prepare Fly.io and DigitalOcean deployments.
  • Interpret JSON results, exit codes, and readiness failures.

Prerequisites

For a local VPS installation, install:
  • Node.js 20.12 or newer.
  • Docker Engine 24 or newer.
  • Docker Compose 2.20 or newer.
  • At least 5 GB of free disk space by default.
  • A public DNS record when using Caddy or an external HTTPS proxy.
Run the host check first:
The check reports Docker, Compose, memory, disk, firewall, and the public ports used by the selected deployment topology.

Install Harly locally

Run the guided installer

On a fresh host, the menu opens the installer. It asks for the public URL, proxy mode, owner email, organization name, storage provider, and resource profile. Before writing files it shows a complete summary. The installer then:
  1. Validates Docker, Compose, ports, disk, memory, firewall, and DNS.
  2. Resolves a pinned Harly release image.
  3. Creates .env, compose.yaml, Caddyfile, harly.config.json, and local operator documentation.
  4. Generates independent runtime secrets and protects .env with mode 0600.
  5. Optionally pulls images and launches the services.
  6. Waits for PostgreSQL, migrations, the web app, the scheduler, and Caddy.
  7. Checks /api/health/ready and reports Harly is ready only after it succeeds.

Generate configuration without launching

init writes configuration but does not pull images or start containers. Run:
To do both steps in one command, add --launch to init.

Configure installer inputs

Public values can be passed as flags: init ./harly and init --output-dir ./harly refer to the same target. If both are provided with different paths, the command fails instead of choosing one silently. S3 values should come from the environment:
The supported environment values are:

Automate the CLI

Global flags work before or after a subcommand when unambiguous:
Use --non-interactive in CI and agent workflows. It never prompts; missing values fail before host or provider operations begin.

JSON output

--json disables prompts and loaders. stdout contains exactly one JSON document for success, failure, and cancellation. Human diagnostics, when enabled, are written to stderr only. Example dry run:
The result is versioned:
Failures use the same schema:
Stable error codes include INVALID_ARGUMENT, MISSING_SECRET, PREFLIGHT_FAILED, READINESS_TIMEOUT, and PARTIAL_PROVISIONING.

Secrets and stdin

Never pass secrets as ordinary flags. Use environment variables or one explicit stdin source per execution:
The CLI never accepts a JSON blob of secrets, never reads stdin implicitly, and never prints secrets in JSON, logs, or error messages.

Dry runs

--dry-run --json resolves the configuration and lists artifacts and planned remote operations. It does not:
  • Write files or create directories.
  • Download images.
  • Run Docker Compose.
  • Call provider APIs.
  • Create cloud resources.
  • Save secrets.

Deploy to managed platforms

Railway

Railway is the fully automated cloud provider. Set secrets through the environment and run:
The CLI creates the project, managed PostgreSQL, volume, web service, scheduler, migration service, runtime variables, domain, and deployments. It waits for the public readiness endpoint before returning status: "ready". Railway secrets are not saved locally by default. Add --save-env only when a local copy is explicitly required:
The resulting file has mode 0600, and the CLI prints a warning. If Railway fails after creating resources, JSON includes status: "failed", error.code: "PARTIAL_PROVISIONING", resourcesCreated, and recovery steps. Resources are not deleted automatically. When --url or --domain is supplied, the generated Railway domain remains the verified deployment URL. The requested custom domain is returned as requestedUrl with an explicit follow-up step to attach and verify it in Railway.

Fly.io and DigitalOcean

These providers currently use a preparation flow and finish with status: "ready-to-deploy":
The CLI writes fly.toml or app.yaml, validates the resolved configuration, and prints the exact provider commands still required. It does not create provider resources. Use --save-env if you explicitly need a local 0600 secrets file.

Operate an installation

Check readiness

Use doctor --fix only in an interactive terminal. It can restart stopped services or bring the Compose project back up.

Back up before updates

The update flow creates a rollback archive, pulls the pinned image, runs the database migration once, restarts the services, and waits for readiness.

Restore or uninstall

Restore replaces the current database and uploads. Back up first. The --remove-data option for uninstall permanently removes PostgreSQL and upload volumes; it is intentionally separate from the default uninstall behavior.

Exit codes

Troubleshooting

Caddy cannot bind ports 80 or 443

Check the process reported by the installer. Stop or reconfigure the existing proxy, or use --proxy external and forward traffic to the Harly host port.

DNS validation fails

Create an A record for the public hostname and allow propagation. Verify it from the VPS with:

The public readiness check times out

Run:
Check the domain, firewall, database migration, and proxy mode before retrying.