Hop3 CLI Reference¶
Version: 0.6.0 Last Updated: 2026-04-17
This document provides a complete reference for all Hop3 CLI commands.
Note (0.5.0 breaking changes). The CLI surface was redesigned under ADR 036. Key changes from 0.4.x:
- Commands use spaces, not colons:
hop3 env set(washop3 config:set).- Implicit
--appresolution chain, with a per-directory app pin (hop3 use <app>).- Alias mechanism:
hop3 apps,hop3 addons,hop3 plugins,hop3 whoamiare built-in aliases (envis a real command group, withconfigas its back-compat alias).- Did-you-mean suggestions on typos for both commands and app names.
hop3 help --all --jsonemits the whole command tree as data (see Machine-readable help).- 11-code exit-code table (see Exit Codes) and the
--no-input,--confirm=<name>,--password-file/--stdinflags for automation.- State-change summary lines on mutations, routed to stderr for pipeline safety.
Table of Contents¶
- Getting Started
- Global Flags
- Context Management
- Authentication Commands
- Application Management
- Environment Variables
- Domain Management
- Nix Commands
- Backup and Restore
- Services (Addons)
- Admin Commands
- Machine-readable help
- System Commands
- Miscellaneous Commands
Getting Started¶
Installation¶
# One-liner installer (recommended)
curl -LsSf https://hop3.cloud/install-cli.py | python3 -
# Or from PyPI
pip install hop3-cli
# Or as an isolated uv tool
uv tool install hop3-cli
Configuration¶
Set your API endpoint and authenticate:
# Set API endpoint
export HOP3_API_URL="https://your-hop3-server.com"
# or
export HOP3_API_URL="ssh://user@your-hop3-server.com"
# Login (interactive; stores the token in the per-server credential store)
hop3 auth login
# The token is stored in the per-server credential store
# ~/.config/hop3-cli/credentials.toml (or override with HOP3_API_TOKEN)
Basic Usage¶
# List all applications
hop3 app list
# Deploy an application
hop3 deploy --app myapp
# View application status
hop3 app status --app myapp
# View logs
hop3 app logs --app myapp
Global Flags¶
All commands support these global flags (per ADR 036 D6). Flags may appear before or after the subcommand.
Output Formatting¶
--json- Output results in JSON format (machine-readable). Implies non-interactive: no prompts or colors. The JSON envelope includeserror.exit_codeso scripts don't need to map error strings.--quiet- Suppress non-essential output (minimal output). Errors and state-change summaries still print.
Interaction¶
-y, --yes- Skip confirmation prompts entirely (auto-confirm destructive operations).--force- Override all safety checks. Coarser than--yes: bypasses the project-mismatch guard, the preview and attached-resource warnings.--confirm=<name>- Scriptable alternative to the interactive typed-name prompt. Pass the resource name you're acknowledging; the command runs without prompting and preserves other safety checks (context warnings, attached-addon detection). Use in preference to--forcewhen you only want to skip the typed-name prompt. Example:hop3 app destroy --app myapp --confirm=myapp.--no-input- Refuse to prompt. If input would be required, the command fails with a one-line instruction naming the flag or env var to use instead. For automation/CI where stdin isn't a terminal. SetsHOP3_NO_INPUT=1so prompt-bearing helpers propagate the choice.
Context and App Selection¶
-c, --context <name>- The context (server) for this command, ahead ofHOP3_CONTEXT, the checkout's binding and the global current context (ADR 042, see Where a command goes). A name that matches no context aborts. There is no--serverflag.-a, --app <name>- Target app explicitly. Always a flag, never positional (D5). If not set, the resolver walks the D7 chain (see App Resolution below).--via-ssh- Send this one command through the context's SSH bootstrap route instead of its everyday address. The rescue path, for when the everyday route is what broke: an expired certificate, a stopped proxy, DNS pointing elsewhere. Refuses when the target has no recorded SSH route; it never falls back to the ordinary route in silence.
Diagnostics¶
--why- Print the resolution trace to stderr and exit (diagnostic-only; the command is NOT executed). Shows how the context was chosen, the server it names (or why there is none), and which source supplied the app. Safe to use with destructive commands:hop3 deploy --whyreports the trace without deploying.--no-alias- Bypass alias resolution.hop3 --no-alias appstreatsappsas a literal command, skipping the built-in alias expansion toapp list.
Verbosity¶
Verbosity controls how much output is displayed. The verbosity level is passed to the server and affects all command output.
| Level | Value | Flags | Description |
|---|---|---|---|
| Quiet | 0 | -q, --quiet |
Minimal output (errors only) |
| Normal | 1 | (default) | Standard output |
| Verbose | 2 | -v, --verbose |
Detailed output (build logs, command details) |
| Debug | 3 | -vv, --debug |
Maximum verbosity (all internal operations) |
Flag stacking: You can use multiple -v flags for increased verbosity:
- -v = verbose (level 2)
- -vv = debug (level 3)
- -vvv = debug (capped at level 3)
Environment variable: Set HOP3_VERBOSITY to control default verbosity:
Explicit flags override the environment variable.
Examples¶
# Get JSON output
hop3 app list --json
# Deploy without confirmation
hop3 deploy --app myapp -y
# Quiet mode (minimal output)
hop3 backup create --app myapp --quiet
# Verbose deployment (see Docker build output)
hop3 -v deploy --app myapp
# Debug mode (maximum verbosity)
hop3 -vv deploy --app myapp
# or
hop3 --debug deploy --app myapp
# Combine flags
hop3 app destroy --app oldapp --yes --quiet
# Set default verbosity via environment
HOP3_VERBOSITY=0 hop3 deploy --app myapp # Quiet mode
# Scriptable typed-name confirmation (no prompt, but still safe)
hop3 app destroy --app oldapp --confirm=oldapp
# Non-interactive pipeline: fail fast if a prompt would appear
cat password.txt | hop3 user add alice alice@ex.com --stdin --no-input
# Ask "why did the CLI pick THAT app/context?"
hop3 --why logs
App Resolution¶
App-scoped commands (like hop3 app logs, hop3 app restart, hop3 env set) don't require an explicit app name. The CLI resolves one by walking this chain in order, stopping at the first source that supplies a value (ADR 036 D7):
--app <name>/-a <name>- explicit flag wins over everything else.$HOP3_APP- environment variable for the current shell session..hop3-appfile - a one-line file in the current directory or any ancestor up to$HOME. Put it in a project repo and everyhop3invocation from within picks up the right app.hop3.toml [cli].app- same search path as.hop3-app, lower priority.hop3.toml [contexts.<name>].app- the app's name in the selected context's block (see What differs per deployment).hop3.toml [metadata].id- the project's canonical name (same value the server uses). The "I'm physically standing in this project" fallback, used when no higher-priority source is set in this directory tree.
Set --why on any app-scoped command to see the trace:
Sticky app:
hop3 use myapp # Pin app 'myapp' for this directory (writes .hop3-app)
hop3 use # Show the currently pinned app
hop3 use --clear # Remove the .hop3-app pin
hop3 use <app> writes a .hop3-app file in the current directory (item 3 of the
App Resolution chain). Once pinned, hop3 app logs,
hop3 app restart, etc. all default to myapp from within that directory tree
without needing --app or a positional argument.
Context Management¶
A context is a server you can reach: a name, an address, and the SSH route that renews its token. Contexts are yours: they live in ~/.config/hop3-cli/config.toml, and a login creates one. hop3.toml describes the app and never says where it is deployed (ADR 042).
What is local is the binding: which context a checkout uses. It is written by hop3 context use into .hop3-local.toml, beside the project's hop3.toml, and gitignored. Switching a checkout from dev to demo touches no committed file.
Where a command goes¶
The CLI chooses the context in this order, stopping at the first that supplies one:
| Priority | Source | Scope |
|---|---|---|
| 1 (highest) | --context <name> / -c <name> |
One command |
| 2 | HOP3_CONTEXT (an .envrc or .env may set it), or the context env that HOP3_API_URL / HOP3_DEV_MODE names |
Current shell |
| 3 | Inside a project: the checkout's binding (hop3 context use) |
One checkout |
| 4 (lowest) | Outside a project: the global current context ([cli].default_context) |
Per developer |
A project is a directory with a hop3.toml at or above where you stand. Inside a project with no binding, every command that reaches a server is refused, unless -c or HOP3_CONTEXT names the context; the global current context is never used there. The refusal names the fix:
Error: this checkout (/home/me/myapp) is not bound to a context.
Bind it: hop3 context use <name> (known: dev, demo, prod)
or for one command: hop3 -c <name> ...
A name resolves or aborts, whichever step supplied it: a HOP3_CONTEXT or a binding naming a removed context stops the command. --context and a different HOP3_API_URL together are refused too.
Every command that reaches a server prints, on stderr, where it goes and why:
--quiet silences the line; --why prints the full trace.
What differs per deployment¶
The app may differ between deployments in three things: its name there, its domains and its environment. These live in hop3.toml, in a block keyed by context name, merged over the base configuration:
[metadata]
id = "myapp"
[contexts.demo]
app = "myapp-demo"
[contexts.demo.domains]
list = ["demo.myapp.com"]
[contexts.demo.env]
LOG_LEVEL = "debug"
[contexts.prod]
[contexts.prod.domains]
list = ["myapp.com", "www.myapp.com"]
A checkout bound to demo deploys myapp-demo with its domains and env, to the server your demo context names. So a team names its contexts alike (dev, demo, prod). In a project that declares blocks, a context matching none of them is refused; a project without blocks deploys its base configuration to any context. domains replaces the top-level [domains]; env merges over [env] key by key. A block never carries server or bootstrap: the CLI and the server refuse them, naming the hop3 context add that replaces them.
hop3 context¶
Bare, it shows where a command run here goes, and why, then the known contexts.
hop3 context add¶
Name a server. A login does it too.
--server- The address every command connects to, e.g.https://hop3.example.comorssh://root@host.--bootstrap- Thessh://route used only to renew an expired token. It lets anhttps://server renew unattended; omit it when the server is itselfssh://.
--app, --domain and --env are refused: they belong in the project's [contexts.<name>] block.
hop3 context list / hop3 context show¶
list shows every context; * marks the one a command here would use, and the global current context is labelled. show [<name>] shows one context's addresses and, inside a project, its [contexts.<name>] block.
$ hop3 context list
* demo https://demo.example.com
dev https://dev.example.com (global current context)
prod https://hop3.example.com
Here: demo (https://demo.example.com), bound in this checkout
hop3 context use¶
Inside a project, use binds this checkout to the context; outside one, it sets the global current context. --global sets the global current context from inside a project. It asks before writing, naming the scope, the file and the address:
$ hop3 context use demo
Bind this checkout (/home/me/myapp) to 'demo' (https://demo.example.com)? [y/N] y
Bound /home/me/myapp to 'demo', in .hop3-local.toml (not committed).
-y / --yes skips the question; without a terminal and without --yes, it refuses. Inside a project whose hop3.toml declares blocks, a context matching none of them is refused here already.
hop3 context unset¶
Removes this checkout's binding, or (outside a project, or with --global) the global current context. Same scope rule, same question.
hop3 context remove / hop3 context rename¶
Both edit config.toml only. rename carries the global current context and this checkout's binding along, and says when the project's hop3.toml has a block under the old name (blocks follow the name, so rename it there too, or keep the old name). remove says when this checkout is still bound to the removed context.
Using contexts¶
# Once per checkout
cd myapp && hop3 context use demo
hop3 deploy # → demo (…), bound in this checkout
# One command elsewhere
hop3 -c prod app logs --app myapp
# One shell, or one directory through direnv
export HOP3_CONTEXT=prod
Destructive-operation safety¶
- The deploy preview shows the context, the server, the app and the domains before a deploy.
- The project-mismatch guard refuses a guarded command when the resolved app does not match the project you stand in and the app came from the environment (an exported
HOP3_APPorHOP3_CONTEXT);--forceoverrides it. - Destructive commands ask for the resource's name, typed.
Environment Variables¶
| Variable | Description |
|---|---|
HOP3_CONTEXT |
The context for this shell (rank 2) |
HOP3_API_URL |
A server address for this shell, as the context env (rank 2); for CI |
HOP3_API_TOKEN |
The token to send, instead of the stored one |
HOP3_SSH_KEY |
Private key for every ssh the CLI spawns: the ssh:// tunnel, hop3 tunnel, hop3 app shell, the hop3 login bootstrap, and --via-ssh. Default: ssh's own keys and config. |
HOP3_SSH_PORT |
SSH port when the server address has none (ssh://root@host). A port in the address, or in a hop3 login root@host:2222 target, wins. |
HOP3_SSH_OPTIONS |
Extra ssh -o values, whitespace-separated, for every ssh the CLI spawns: "ProxyJump=bastion UserKnownHostsFile=/path". |
Files and where things live (ADR 042)¶
| File | Scope | Holds | Secret? |
|---|---|---|---|
hop3.toml |
committed, shared | the app: base config and per-deployment [contexts.<name>] blocks (app, domains, env) |
no |
~/.config/hop3-cli/config.toml |
per-developer | contexts (server, bootstrap), [cli].default_context, preferences, aliases |
no |
~/.config/hop3-cli/credentials.toml |
per-developer | bearer tokens keyed by server address | yes (local) |
.hop3-local.toml (gitignored) |
per-checkout | [local].context: the binding |
no |
Contexts (~/.config/hop3-cli/config.toml):
[cli]
default_context = "dev" # the global current context, used outside a project
[contexts.dev]
server = "https://dev.example.com"
bootstrap = "ssh://root@dev.example.com"
[contexts.prod]
server = "https://hop3.example.com"
Credential store (~/.config/hop3-cli/credentials.toml): bearer tokens keyed by the canonical server address, written only by hop3 auth login / hop3 init, created 0o600; you never edit it by hand:
Authentication Commands¶
hop3 user add¶
Register a new user account. Only an admin can.
Usage:
Arguments:
- username - Desired username (alphanumeric, underscores, hyphens)
- email - Valid email address
- password - Password (minimum 8 characters recommended); prefer --stdin or --password-file, which keep it out of shell history
- --role ROLE - member (the default), viewer, deployer or admin; --admin is short for --role admin
Example:
Notes:
- The first account is created by the installer, or by hop3 login --ssh on a server that has none, and it is an admin
- Passwords are hashed with bcrypt (work factor 12)
- Email must be unique
hop3 auth login¶
Log in to a server. hop3 login is the short form of this command.
This is the interactive, full-featured login: it supports SSH bootstrap, token
URLs, magic links (--web) and password entry, and it stores the resulting
token in the per-server credential store (credentials.toml). It also makes that
server the default target for project-less commands. Pass --context <name> to
name that server as a global context (config.toml) and make it the default
context in one step, so hop3 app list --context <name> works afterwards with no
project. It takes no positional username/password; for a non-interactive,
scriptable token use hop3 auth get-token.
Usage:
hop3 login root@server # SSH bootstrap (no password needed)
hop3 login --context devel root@server # same, recorded as the context 'devel'
hop3 login --context devel # again, through the SSH route 'devel' records
hop3 login # password (prompted) for the current server
hop3 login --token <tok> --url <url> # pre-generated token + server address
hop3 login --browser # magic link for the web dashboard
Notes:
- The target is positional. SSH is the only transport a bootstrap has, so naming it in a flag said nothing; --ssh <target> is the old spelling and still works, as does --web for --browser.
- The login asks the server for its own public address. With an admin domain and a certificate this machine trusts, that becomes the everyday address (https://…) and the SSH target is kept only to renew the token. Without one, everything tunnels over SSH and the login says why.
- Token stored in the per-server credential store (~/.config/hop3-cli/credentials.toml), keyed by the server address.
- The server is recorded as a global context (default when --context <name> is not given) and becomes the current target. Logging a different address into default without naming it is refused.
- Token valid for 24 hours by default (HOP3_TOKEN_EXPIRY_HOURS on the server); the Web UI's browser session lasts 14 days and is a separate credential. An expired token is re-minted over the recorded SSH route without asking.
- Set HOP3_API_TOKEN environment variable to override; HOP3_API_URL declares the context named env, which outranks anything stored.
hop3 auth get-token¶
Verify credentials and print an API token, for scripts and automation. It does not save anything; capture the token yourself. Pass the password without putting it on the command line.
Usage:
hop3 auth get-token <username> --password-file - # read password from stdin
hop3 auth get-token <username> --password-file pw # read password from a file
Example:
hop3 auth whoami¶
Display current authenticated user information.
Usage:
Example Output:
hop3 auth logout¶
Logout and invalidate current token.
Usage:
Notes:
- Revokes the token on the server, then removes it from the per-server credential store (~/.config/hop3-cli/credentials.toml)
- If the server can't be reached to revoke, the local token is still cleared and the revoke failure is reported
Application Management¶
hop3 app list¶
List all applications.
Usage:
Example Output:
┌─────────────────────────────────────────────────────────────┐
│ Applications │
├────────────┬────────────┬──────────────────────┬────────────┤
│ Name │ Status │ URL │ Deployed │
├────────────┼────────────┼──────────────────────┼────────────┤
│ myapp │ RUNNING │ https://myapp.com │ 2 days ago │
│ testapp │ STOPPED │ https://test.app.com │ 1 week ago │
└────────────┴────────────┴──────────────────────┴────────────┘
JSON Output:
{
"apps": [
{
"name": "myapp",
"status": "RUNNING",
"url": "https://myapp.com",
"deployed_at": "2025-11-10T14:30:00Z"
}
]
}
hop3 app create¶
Create and configure a new app from a Git repository. hop3 app launch is a back-compat alias.
Usage:
Arguments:
- repo_url - Git repository URL: https://, http://, ssh://, git://, or the scp form git@host:user/repo.git
- --app <app_name> - Name for the application (alphanumeric, hyphens, underscores)
Example:
Notes:
- Clones repository to server
- Does not deploy automatically (use hop3 deploy afterwards)
- Repository must be accessible from server
- The clone is shallow (--depth 1, default branch only) and is stopped if it
runs past 10 minutes or 2 GiB. A local path, and git's ext:: transport, are
refused: both let a repository address name something other than a repository.
- If the clone fails, the app is not created; nothing is left to clean up
hop3 deploy¶
Deploy an application from uploaded source or configured repository.
Usage:
Arguments:
- --app <app_name> - Name of application to deploy (else resolved from context)
- directory - Source directory (default: current directory)
Options:
- --env KEY=VALUE or -e KEY=VALUE - Set environment variable (can be repeated)
- --no-stream - Disable real-time log streaming (use batch output)
- --stream - Enable real-time log streaming (default)
Examples:
# Deploy from current directory (app from context)
cd myapp/
hop3 deploy --app myapp
# Deploy with environment variables
hop3 deploy --app myapp --env LOG_LEVEL=info --env MAX_WORKERS=4
# Deploy from a specific directory
hop3 deploy --app myapp ./src
# Disable streaming (batch output at end)
hop3 deploy --app myapp --no-stream
Real-time Log Streaming:
By default, hop3 deploy streams deployment logs in real-time via Server-Sent Events (SSE). You'll see build output as it happens:
> Starting deployment for app 'myapp'
-> Using builder: 'LocalBuilder'
--> Creating virtualenv...
--> Installing from requirements.txt
Collecting Flask==3.0.0
Successfully installed Flask-3.0.0
-> Build successful
-> Using deployment strategy: 'uwsgi'
> Waiting for app 'myapp' to start (timeout: 60s)...
> App 'myapp' is now running.
✓ Deployment completed successfully in 45.2s
Use --no-stream to fall back to batch output (all logs shown at end).
Process: 1. Uploads source code as tarball 2. Extracts on server 3. Detects language/framework (Python, Node.js, Ruby, Go, Static) 4. Builds application (installs dependencies, compiles assets) 5. Configures reverse proxy (nginx, Caddy, or Traefik) 6. Starts application processes
Startup Timeout:
Apps must start within a configurable timeout (server default: 60 seconds). Configure per-app in hop3.toml:
Or change the server-wide default via the APP_START_TIMEOUT environment variable.
Notes:
- A hop3.toml configures the build and runtime, and the detected toolchain supplies a default process model, so most apps deploy without any process file
- Automatically detects the toolchain based on files present
- Use -v or -vv for more verbose output (see Global Flags)
- Build logs are also saved and can be retrieved with app build-logs
- Streaming requires direct HTTP connection (SSH tunnel falls back to batch mode)
- See Packaging Applications for details
hop3 app status¶
Show detailed status of an application. hop3 status is a top-level alias.
Usage:
Example Output:
Application: myapp
Status: RUNNING
Hostname: myapp.example.com
Port: 8000
Processes:
web: 2 running
worker: 1 running
Memory Usage: 245 MB
Uptime: 3 days 14 hours
hop3 app logs¶
Show application logs.
Usage:
Options:
- --app <app> - Target application (else resolved from context)
- -n N, --lines N - Number of lines to show (default: 100)
- --grep PATTERN - Show only lines containing PATTERN (case-insensitive substring)
- --since-deploy - Show only logs since the last deployment
- --build - Show build logs instead of runtime logs (equivalent to app build-logs)
Example:
# Show last 100 lines (app from context)
hop3 app logs
# Show last 500 lines for an explicit app
hop3 app logs --app myapp -n 500
# Filter to lines containing 'error'
hop3 app logs --app myapp --grep error
# Only logs since the last deploy
hop3 app logs --app myapp --since-deploy
hop3 app build-logs¶
Show build logs for an application (Docker build output). Equivalent to hop3 app logs --build.
Usage:
Example:
Example Output:
=== Docker Build Log ===
Timestamp: 2025-12-09 14:30:22
App: myapp
Status: SUCCESS
Duration: 45.3s
=== STDOUT ===
#1 [internal] load build definition from Dockerfile
#2 [internal] load .dockerignore
#3 [1/5] FROM debian:bookworm-slim
...
=== STDERR ===
Notes:
- Shows the most recent Docker build output
- Useful for debugging deployment failures
- Logs are stored in {app_path}/log/build.log
- Use deploy -v or deploy --debug to see output during deployment
hop3 app restart¶
Restart an application. hop3 restart is a top-level alias.
Usage:
Example:
Notes: - Graceful restart (waits for requests to complete) - Reloads environment variables - Zero-downtime for apps with multiple processes
hop3 app start¶
Start a stopped application.
Usage:
hop3 app stop¶
Stop a running application.
Usage:
Notes: - Gracefully stops all processes - Application remains configured (can be restarted)
hop3 app debug¶
Show comprehensive debug information for an application.
Usage:
Notes: - Collects environment, logs, process status, and configuration - Useful for troubleshooting deployment issues
hop3 app ping¶
Check if an application is responding to HTTP requests.
Usage:
Examples:
Notes: - Performs HTTP health check on the application - Returns response status and time
hop3 app destroy ⚠️¶
DESTRUCTIVE - Destroy an app, removing all files and configuration. hop3 destroy is a top-level alias.
Usage:
Confirmation Required:
WARNING: This will permanently delete the app 'myapp' and all its data.
Type the app name to confirm: myapp
What Gets Deleted:
- All source code
- All data in /data directory
- All environment variables
- All attached services (credentials removed)
- All backups
- Reverse proxy configuration
Skip Confirmation:
⚠️ WARNING: This operation is irreversible. Always backup before destroying.
Environment Variables¶
The canonical command group is env. config is a back-compat alias, so every hop3 env <sub> below also works as hop3 config <sub>.
The target app is resolved from context when omitted; pass --app <name> to target a specific app explicitly.
hop3 env show¶
Show all environment variables for an app.
Usage:
Pass --sources to add a column showing where each variable comes from
(addon vs config); this replaces the former hop3 app env.
Example Output:
Environment Variables for myapp:
DATABASE_URL=postgresql://user:pass@localhost/db
REDIS_URL=redis://localhost:6379
SECRET_KEY=***hidden***
LOG_LEVEL=info
Notes:
- Sensitive values masked by default
- Use env get to retrieve specific values
hop3 env get¶
Get a specific environment variable value.
Usage:
Example:
hop3 env set¶
Set environment variables for an app.
Usage:
hop3 env set [--app <app>] KEY1=value1 [KEY2=value2 ...]
hop3 env set [--app <app>] KEY=- # value read from stdin
hop3 env set [--app <app>] KEY=@<path> # value read from a file
hop3 env set [--app <app>] KEY # prompt, input hidden
Arguments:
- --app <app> - Target application (else resolved from context)
- KEY=value - One or more key-value pairs
Examples:
# Set single variable
hop3 env set --app myapp LOG_LEVEL=info
# Set multiple variables
hop3 env set --app myapp \
DATABASE_URL=postgresql://localhost/db \
REDIS_URL=redis://localhost:6379
# Set variable with spaces (quote the value)
hop3 env set --app myapp MESSAGE="Hello World"
Secrets. A value written as KEY=value is recorded in your shell history and
is visible in ps while the command runs. Use one of the three secret-safe forms
instead (ADR 036 §D14):
# Prompt, with no echo — the interactive form
hop3 env set --app myapp SENTRY_DSN
# From a file — the scripted form
hop3 env set --app myapp SENTRY_DSN=@/run/secrets/sentry-dsn
# From stdin — the pipeline form
pass show sentry/dsn | hop3 env set --app myapp SENTRY_DSN=-
A value that legitimately begins with @ has to come through one of those forms
too, or it will be read as a file path: printf '@handle' | hop3 env set --app myapp X=-.
Notes:
- Requires app restart to take effect: hop3 app restart --app myapp
- Values are stored encrypted in database
- No leading/trailing whitespace in keys
- The confirmation line redacts the value it just set (and the one it replaced)
hop3 env unset¶
Unset (remove) environment variables for an app.
Usage:
Arguments:
- --app <app> - Target application (else resolved from context)
- KEY - One or more keys to remove
Examples:
# Remove single variable
hop3 env unset --app myapp DEBUG
# Remove multiple variables
hop3 env unset --app myapp OLD_KEY DEPRECATED_VAR UNUSED_SECRET
hop3 env live¶
Show live runtime environment of running app.
Usage:
Options:
- --show-secrets - Reveal full values instead of redacting secrets
Notes:
- Shows environment as currently loaded by running processes
- Useful for debugging configuration issues
- Fails with a clear error if the running environment can't be inspected (use env show for configured values)
hop3 app migrate¶
Migrate configuration from another PaaS format to hop3.toml. The canonical command is hop3 app migrate; hop3 env migrate and hop3 config migrate are aliases. The source format and app directory are positional arguments.
Usage:
Arguments:
- from-format - Source format. Currently only procfile is supported.
- app-dir - Path to the application directory containing the source file.
Options:
- --dry-run - Preview the generated hop3.toml without writing it
- --backup - Back up the original file before writing (on by default)
Example:
# Preview the generated hop3.toml
hop3 app migrate procfile /path/to/app --dry-run
# Apply the migration
hop3 app migrate procfile /path/to/app
Domain Management¶
Manage the hostnames bound to an app. These commands are a first-class view over the HOST_NAME env var that the reverse-proxy plugins (nginx / caddy / traefik) read. All write operations are atomic: every hostname is validated and conflicts with other apps are checked up front before anything is persisted. After every write you must redeploy (hop3 deploy --app <app>) for the proxy configuration to be updated.
For the declarative equivalent in hop3.toml, see [domains].
hop3 domain list¶
Show the hostnames currently bound to an app.
Usage:
Example:
hop3 domain add¶
Add one or more hostnames to an app (union, atomic, deduplicated).
Usage:
Example:
hop3 domain remove¶
Remove one or more hostnames from an app. Errors if any of the requested hostnames is not currently bound.
Usage:
hop3 domain set¶
Replace the full list of hostnames bound to an app.
Usage:
Example:
hop3 domain clear¶
Clear all hostnames from an app (unsets HOST_NAME).
Usage:
Nix Commands¶
hop3 nix eject¶
Print the hop3.nix a [nix] template generates for a deployed app, to take it over by hand. Once you commit it and remove the [nix] section from hop3.toml (a build refuses an app that has both), the NixBuilder uses the committed hop3.nix instead of regenerating from the template.
Use nix eject when you've outgrown the templates and need to customise the generated Nix expression directly.
Usage:
Behavior:
- Reads the [nix] section from the deployed app's hop3.toml
- Generates the Nix expression using the same template engine that
the NixBuilder uses at deploy time
- Writes it to stdout, with a header noting which template it came from
and the date; nothing changes on the server
Example:
# Eject the generated Nix for the "myapp" deployment
hop3 nix eject --app myapp > hop3.nix
# Remove the [nix] section from hop3.toml, edit hop3.nix freely, commit,
# and deploy
Errors:
- "App has no hop3.toml" — the app source has no hop3.toml
- "No [nix].template in hop3.toml" — the app isn't using template mode
See also:
- Nix deployment guide
- hop3.toml [nix] section
Backup and Restore¶
hop3 backup create¶
Create a backup of an application.
Usage:
Options:
- --app <app> - Application to backup (else resolved from context)
- --no-addons - Skip backing up attached addons (databases, etc.)
Example:
What Gets Backed Up:
- Application source code (git archive if available)
- Data directory (/data)
- Environment variables
- Attached services (database dumps, etc.)
Output:
Creating backup for myapp...
├─ Backing up source code... ✓
├─ Backing up data directory... ✓
├─ Backing up environment variables... ✓
└─ Backing up services (postgres: myapp-db)... ✓
Backup created: 20251112_143022_a8f3d9
Location: /home/hop3/.hop3/backups/apps/myapp/20251112_143022_a8f3d9/
Size: 45.2 MB
See Also: Backup and Restore Guide
hop3 backup list¶
List all backups, optionally filtered by application.
Usage:
Examples:
Example Output:
┌───────────────────────────────────────────────────────────────────┐
│ Backups for myapp │
├─────────────────────────────┬──────────┬───────────────┬──────────┤
│ Backup ID │ Size │ Created │ Services │
├─────────────────────────────┼──────────┼───────────────┼──────────┤
│ 20251112_143022_a8f3d9 │ 45.2 MB │ 2 hours ago │ postgres │
│ 20251110_091534_b2c7e1 │ 43.1 MB │ 2 days ago │ postgres │
└─────────────────────────────┴──────────┴───────────────┴──────────┘
hop3 backup show¶
Show detailed information about a backup. hop3 backup info is a back-compat alias.
Usage:
Example Output:
Backup Information
Backup ID: 20251112_143022_a8f3d9
Application: myapp
Created: 2025-11-12 14:30:22 UTC (2 hours ago)
Size: 45.2 MB
Contents:
├─ Source code: 2.1 MB (git commit: abc123f)
├─ Data directory: 15.3 MB (145 files)
├─ Environment variables: 12 variables
└─ Services:
└─ postgres (myapp-db): 27.8 MB
Checksums (SHA256):
├─ source.tar.gz: 3f7a8b2c...
├─ data.tar.gz: 9d4e1a5f...
└─ services/postgres-myapp-db.sql: 7c2b9e4a...
hop3 backup restore¶
Restore an application from a backup.
Usage:
Arguments:
- backup_id - ID of backup to restore
Options:
- --target-app - Restore to a different app name (default: original app name)
Examples:
# Restore to original app (overwrites existing)
hop3 backup restore 20251112_143022_a8f3d9
# Restore to a new app name
hop3 backup restore 20251112_143022_a8f3d9 --target-app myapp-restored
Process: 1. Creates application if it doesn't exist 2. Restores source code 3. Restores data directory 4. Restores environment variables 5. Restores services (databases, etc.) 6. Verifies checksums
Notes:
- Does not automatically start the app (use hop3 app restart --app <app>)
- Restoring to existing app overwrites data (confirmation required)
hop3 backup destroy ⚠️¶
DESTRUCTIVE - Delete a backup.
Usage:
Confirmation Required:
WARNING: This will permanently delete the backup '20251112_143022_a8f3d9'.
Type 'DELETE' to confirm: DELETE
Skip Confirmation:
Services (Addons)¶
Services are backing infrastructure (databases, caches, queues) that can be attached to applications.
hop3 addon create¶
Create a new backing service instance.
Usage:
Arguments:
- service_type - Type of service (postgres, redis, etc.)
- service_name - Name for this service instance
Example:
Output:
Service 'myapp-db' of type 'postgres' created successfully.
To attach this service to an app, run:
hop3 addon attach myapp-db --app <app-name>
Notes:
- Service created but not yet attached to any app
- Credentials generated and stored encrypted
- Use addon attach to connect to an application
hop3 addon attach¶
Attach a service to an application.
Usage:
Arguments:
- service_name - Name of service to attach
- --app - Name of application
Options:
- --service-type (or --type) - The addon's type, needed only when two addons of different types share the name; otherwise the name decides
- --primary - Make this the primary addon of its type for the app (see below)
Example:
Multiple addons of the same type: the first addon of a type attached to
an app is the primary and injects the unprefixed connection vars
(DATABASE_URL, …). A second addon of the same type is secondary and its
vars are prefixed with <ADDONNAME>_ (e.g. REPLICA_DATABASE_URL), so the two
never clobber each other. Use --primary to attach-and-promote (demoting the
current primary), or hop3 addon promote later.
What Happens: - Service connection details added as environment variables (namespaced as above) - Credentials stored encrypted in database - App must be redeployed to use new variables
hop3 addon detach¶
Detach a service from an application.
Usage:
Example:
Notes:
- Removes the addon's environment variables from the app (both the unprefixed
and any prefixed spelling).
- Does not destroy the service itself; credentials removed from the app.
- If you detach the primary addon and same-type siblings remain, the oldest
sibling is auto-promoted to primary (so the app keeps an unprefixed
DATABASE_URL); this is reported in the output.
hop3 addon destroy ⚠️¶
DESTRUCTIVE - Destroy a service instance.
Usage:
Warning:
WARNING: This will permanently delete all data in service 'myapp-db'!
Type the service name to confirm: myapp-db
What Gets Deleted: - All data in the service (database, cache, etc.) - All credentials across all apps - Service configuration
Notes:
- Service must be detached from all apps first (or use --force)
- Backups are NOT automatically created (use backup create first)
hop3 addon show¶
Get information about a service instance.
Usage:
Example Output:
Service: myapp-db
Type: postgres
Status: Running
Version: PostgreSQL 14.5
Size: 127 MB
Tables: 15
Connections: 3 active
hop3 addon list¶
List provisioned addon instances (aliased to hop3 addons).
Usage:
Options:
- --app - Only addons attached to this application
- --type - Filter by addon type (postgres, mysql, redis)
Example:
# List all instances on the server
hop3 addon list
# List addons attached to an app
hop3 addon list --app my-app
# List PostgreSQL instances
hop3 addon list --type postgres
hop3 addon types¶
List the addon types that can be provisioned with hop3 addon create.
Usage:
hop3 addon status¶
Show detailed status and health of an addon.
Usage:
Notes:
- Shows connection status, health checks, and resource usage
- More detailed than addon show
hop3 addon endpoint¶
Show an addon's connection endpoint (URL, host, port). Type-agnostic: the addon's type is resolved from its name, so no --type is needed. This is what hop3 tunnel uses under the hood, but it's also handy on its own.
Usage:
Notes: - Prints the connection URL plus host and port. - Errors if the name is unknown, or ambiguous across two addon types.
hop3 addon exists¶
Predicate for scripts/CI: exits 0 if the addon exists, 1 if it doesn't. Prints nothing in normal mode (use the exit code); with --json it also prints {"exists": true|false}. Type-agnostic; pass --type to require a specific type.
Usage:
Examples:
hop3 addon exists mydb && hop3 addon promote mydb --app web
hop3 addon exists mydb --type postgres
hop3 addon exists mydb --json # -> {"exists": true}
hop3 addon expose¶
Make an addon reachable from outside the server on a stable, persisted host port, and print a connection URL. The addon normally listens only on 127.0.0.1; expose allocates a public port, opens the firewall for it, and starts a per-addon systemd-socket-proxyd forwarder to the addon's loopback port. The port survives server and addon restarts. Type-agnostic (the type is resolved from the name).
Usage:
Options:
- --source <cidr|any> - Required. Who may reach the port: a CIDR (e.g. 203.0.113.0/24), or any to open it to the whole internet. Set EXPOSE_DEFAULT_SOURCE in hop3-server.toml to make a CIDR the per-server default; with no default and no flag, the command refuses (no accidental public database).
- --host <fqdn> - External hostname for the printed URL. Defaults to the server's canonical domain (ADMIN_DOMAIN); required if that isn't set.
- --type <type> - Disambiguate when one name exists across two addon types.
Examples:
hop3 addon expose mydb --source 203.0.113.0/24
# -> postgresql://user:pass@db.example.com:54312/mydb
hop3 addon expose mydb --source any --host db.example.com
Notes:
- The returned URL contains the addon password; treat it as a secret.
- --source any exposes the database to the entire internet; only the addon credentials protect it. Scope with a CIDR when you can.
- Idempotent: re-running returns the existing endpoint (no second port).
- hop3 addon destroy automatically unexposes first.
hop3 addon unexpose¶
Remove an addon's public exposure: close the firewall port, remove the forwarder, and free the claim. Idempotent; the addon and its data are untouched.
Usage:
hop3 addon promote¶
Make an addon the primary one of its type for an app. When several same-type addons are attached, the primary injects the unprefixed connection vars (DATABASE_URL, …) and the others are prefixed (<NAME>_DATABASE_URL). This flips which one is primary; the previous primary becomes prefixed. Type-agnostic (type resolved from the name).
Usage:
Example:
hop3 addon promote replica-db --app myapp
# -> replica-db now owns DATABASE_URL; the old primary becomes <OLD>_DATABASE_URL
Notes:
- Idempotent: promoting the addon that is already primary is a no-op.
- Errors if the addon isn't attached to the app, or the type is ambiguous (pass --type).
- Redeploy the app for the env change to take effect.
hop3 addon <type> <verb>: type-specific commands¶
Beyond the type-agnostic verbs above, each addon type exposes a few operations specific to it, under hop3 addon <type> <verb> <name>. The addon's type is part of the command path, so no --type flag is needed.
Usage:
# PostgreSQL
hop3 addon postgres credentials <name> # Show connection env vars
hop3 addon postgres dump <name> # Back up via pg_dump
hop3 addon postgres restore <name> <path> # Restore via psql ⚠️ overwrites
hop3 addon postgres extensions <name> <ext>... # Install extensions (allow-listed)
hop3 addon postgres query <name> --command "SELECT 1" # Ad-hoc SQL
hop3 addon postgres clone <source> <new-name> # Copy data into a new addon
hop3 addon postgres export <name> > dump.sql # Stream a dump to your machine
hop3 addon postgres import <name> --confirm=<name> < dump.sql # Load a dump
hop3 addon postgres activity <name> # Active queries (diagnostics)
hop3 addon postgres locks <name> # Current locks
hop3 addon postgres settings <name> # Key configuration settings
# MySQL
hop3 addon mysql credentials <name>
hop3 addon mysql dump <name> # mysqldump
hop3 addon mysql restore <name> <path> # ⚠️ overwrites
hop3 addon mysql query <name> --command "SELECT 1"
hop3 addon mysql clone <source> <new-name> # Copy data into a new addon
hop3 addon mysql export <name> > dump.sql # Stream a dump to your machine
hop3 addon mysql import <name> --confirm=<name> < dump.sql # Load a dump
hop3 addon mysql activity <name> # Active queries (diagnostics)
hop3 addon mysql settings <name> # Key variables
# Redis
hop3 addon redis credentials <name>
hop3 addon redis dump <name>
hop3 addon redis restore <name> <path> # ⚠️ overwrites
hop3 addon redis flush <name> # FLUSHDB ⚠️ deletes all keys
hop3 addon redis query <name> --command "DBSIZE" # Ad-hoc redis-cli command
hop3 addon redis clone <source> <new-name> # Copy data into a new addon
hop3 addon redis export <name> > dump.rdb # Stream a dump to your machine
hop3 addon redis import <name> --confirm=<name> < dump.rdb # Load a dump
hop3 addon redis info <name> # Server INFO (diagnostics)
# S3
hop3 addon s3 credentials <name>
hop3 addon s3 dump <name> # Archive the objects (tar)
hop3 addon s3 restore <name> <path> # ⚠️ overwrites
hop3 addon s3 clone <source> <new-name> # Copy data into a new addon
hop3 addon s3 export <name> > dump.tar # Stream a dump to your machine
hop3 addon s3 import <name> --confirm=<name> < dump.tar # Load a dump
Available verbs by type:
| Verb | postgres | mysql | redis | s3 |
|---|---|---|---|---|
credentials |
✓ | ✓ | ✓ | ✓ |
dump |
✓ | ✓ | ✓ | ✓ |
restore |
✓ | ✓ | ✓ | ✓ |
extensions |
✓ | |||
flush |
✓ | |||
query |
✓ | ✓ | ✓ | |
clone |
✓ | ✓ | ✓ | ✓ |
export / import |
✓ | ✓ | ✓ | ✓ |
ps |
✓ | ✓ | ||
locks |
✓ | |||
settings |
✓ | ✓ | ||
info |
✓ |
Notes:
- credentials prints the addon's connection variables (DATABASE_URL, REDIS_URL, S3_*, …); treat the output as sensitive.
- restore and redis flush are destructive and prompt for confirmation (bypass with -y / --confirm=<name>).
- dump writes to the server's backup area (/home/hop3/backups/<type>/) and reports the path. restore takes a path on the server, inside /home/hop3/backups, and refuses any other: to load a dump from your machine, use import. An s3 dump is a tar of the bucket's objects with their content types; its restore writes every object back, then deletes the ones the dump does not hold. An s3 dump from before 0.8 holds no objects, and its restore is refused.
- query runs the statement as the addon's own (least-privilege) database user, confined to that addon's database. A SELECT renders as a table; other statements report the affected row count. The SQL/command is passed via --command "…".
- clone creates a brand-new addon and loads a dump of the source into it (it refuses if the target already exists). It builds on restore.
- export streams a dump to the client's stdout (redirect to a file); import reads a dump from the client's stdin. Because import overwrites data and stdin is the dump (so it can't prompt), pass --confirm=<name> or --yes with it. A PostgreSQL dump needs no OWNER TO lines: a dump taken with pg_dump --no-owner, or on another platform, is given to the addon's user.
- ps / locks / settings (postgres, mysql) and redis info are read-only diagnostics. The SQL ones run as the superuser so they see the whole database, and render as tables; redis info returns the server's INFO text.
Web Application Firewall (WAF)¶
These manage the Layer-7 WAF's runtime state. The per-app policy (enable, allow/gate/tuning/bans) lives in hop3.toml under [waf] (see the config reference). An app's WAF turns on at deploy when [waf].enabled = true.
hop3 waf status¶
Per-app proxy port, whether the proxy is supervised, and the active ban count, for every WAF-enabled app.
hop3 waf logs [<app>]¶
Recent blocked-request audit entries (time, source, action, rule, path) across all WAF-enabled apps, or just one.
hop3 waf bans list [<app>]¶
Active bans (app, source, reason, expiry). Bans are recorded by the scorer when a source crosses the [waf.bans] threshold within the window.
hop3 waf bans clear <app> [<ip>]¶
Lift all bans for an app, or one source IP. Rewrites the app's denylist and reloads the proxy. Destructive: prompts unless --yes.
hop3 waf reconcile-bans¶
Force a ban-scoring pass across all WAF-enabled apps now: reads each audit stream, bans repeat offenders, expires elapsed bans, and reloads changed proxies. The server already runs this in-process every ~60s (waf_bans_service); this command is the manual/debug entry point. Safe to run repeatedly.
hop3 network add|list|rm¶
Operator-defined named networks (CIDR sets) referenced by a [[waf.gate]] require = "<name>":
Admin Commands¶
Admin commands require the admin role. The server's first account, created by the installer or by hop3 login --ssh on a server that has none, is an admin.
hop3 user list¶
List all user accounts.
Usage:
Example Output:
Users
================================================================================
Username Email Active Role Logins
--------------------------------------------------------------------------------
alice alice@example.com Yes admin 12
bob bob@example.com Yes deployer 3
charlie charlie@example.com No viewer 0
Total users: 3
hop3 user add¶
Create a new user account. See hop3 user add above for the options.
Usage:
hop3 user show¶
Display detailed information about a user.
Usage:
hop3 user set-password¶
Reset a user's password.
Usage:
hop3 user disable¶
Disable a user account (prevents login).
Usage:
hop3 user enable¶
Enable a disabled user account.
Usage:
hop3 user remove¶
Remove a user account.
Usage:
hop3 user set-role¶
Set a user's role: member, viewer, deployer or admin. An admin cannot change their own role; another admin must, so the server always keeps one.
Usage:
hop3 user grant-admin¶
Make a user an admin. Same as hop3 user set-role <username> admin.
Usage:
hop3 user revoke-admin¶
Take the admin role from a user, leaving them a deployer. Use hop3 user set-role for any other role.
Usage:
hop3 user generate-token¶
Generate a new API token for a user (bootstrap helper).
Usage:
Example Output:
Notes: - Useful for CI/CD or automated scripts - Token does not expire by default (set expiration in config)
System Commands¶
Four subcommands answer four distinct questions about the server:
| Command | Answers |
|---|---|
system status |
Is the server OK?: full health report + identity header |
system info |
What is this server?: static facts (version, OS, IPs) |
system logs |
What happened?: server log tail with filters |
system cleanup |
Reclaim Docker resources: networks, images, build cache |
The pre-0.5 commands system check, system uptime, and system ps were removed: check was renamed to status (it was always the rich health view), uptime is now part of the status and info identity header, and ps returned the entire host's process table over RPC and was removed as a security smell. For per-app process info, use hop3 ps --app <app>.
hop3 system status¶
Show full health status of the Hop3 server. Default output: one-line identity header (host, IP, version, uptime) followed by per-section health items, then a bottom-line summary.
Usage:
Options:
- --quiet, -q: One-line summary only. For scripts.
- --json: Machine-readable JSON. For dashboards and external monitors.
Exit code: non-zero when there is any warning or failure (the command emits an error/warning response item).
Example output:
Hop3 server: hop3-dev (135.181.203.156) — v0.5.0.dev3 — up 14d 3h
Services
Hop3 Server ✓ running
Nginx ✓ running
uWSGI Emperor ✓ running
Backing services
PostgreSQL ✓ ok
MySQL ✓ ok
Redis ⚠ unreachable — connection refused (127.0.0.1:6379)
S3 (minio) ✓ ok
Filesystem
HOP3_ROOT ✓ writable
Apps directory ✓ writable
Disk usage ⚠ 86%
Certificates
SSL ⚠ self-signed (Let's Encrypt not configured)
Status: ⚠ 2 warnings
Severity legend: ✓ ok · ⚠ warning · ✗ failure. Optional services (Redis, Docker) report ⚠ when unreachable and ✗ never appears for them, so the overall status stays degraded, never failed.
hop3 system info¶
Show static facts about this server. No liveness probes; for "is everything OK?", use system status.
Usage:
Options:
- --verbose, -v: Also list loaded plugins (builders, deployers, toolchains) and key filesystem paths.
Example output:
Version: 0.5.0.dev3
Python: 3.12.3
Platform: Linux 6.8.0-117-generic
Hostname: hop3-dev
IP Addresses: 135.181.203.156
Uptime: 14d 3h
Docker: installed
hop3 system logs¶
Show Hop3 server logs from the default log file, with optional time-window, level, and regex filters.
Usage:
Options:
- -n, --lines N: Number of lines to show (default: 100).
- --since DURATION: Show logs since a duration ago (e.g. 1h, 30m, 1d).
- --level LEVEL: Filter by log level (DEBUG, INFO, WARNING, ERROR).
- --grep PATTERN: Filter lines matching a regex (case-insensitive).
hop3 system cleanup¶
Reclaim unused Docker resources: stopped containers, unused networks, dangling images, build cache.
Usage:
Options:
- --dry-run: Show what would be cleaned without doing it.
- --all: Also remove unused images (not just dangling).
- --volumes: Also prune unused volumes. May cause data loss.
Miscellaneous Commands¶
hop3 help¶
Display help information.
Usage:
hop3 help [command]
hop3 help --all # Flat index of every command, with markers
hop3 help --all -v # Full help for every command, aggregated
Examples:
# General help
hop3 help
# Help for specific command
hop3 help deploy
hop3 help backup create
# Flat alphabetical index of every command (top-level + namespaced)
hop3 help --all
# One long document aggregating the full help for every command,
# recursively (server commands + client-side local commands). Handy for
# piping to a file or a pager:
hop3 help --all -v | less
hop3 help --all --verbose > hop3-commands.txt
hop3 help commands¶
Return a list of available command names for shell completion.
Usage:
Notes: - Returns plain text list of command names - Used internally by shell completion scripts
hop3 completion¶
Generate shell completion scripts.
Usage:
Arguments:
- shell - Shell type: bash, zsh, or fish
Installation Examples:
# Bash (current session)
eval "$(hop3 completion bash)"
# Bash (permanent)
hop3 completion bash > /etc/bash_completion.d/hop3
# Zsh
hop3 completion zsh > ~/.zsh/completions/_hop3
# Fish
hop3 completion fish > ~/.config/fish/completions/hop3.fish
Options:
- --refresh - Update cached command list from server
- --status - Show cache status
hop3 plugin list¶
List installed plugins and their commands.
Usage:
hop3 tunnel¶
Open a local SSH tunnel to a remote addon and print a ready-to-paste local connection URL. This is a client-side command: it forwards a local port to the addon's port on the server over the configured SSH connection, then holds the tunnel open until you press Ctrl-C. The addon's type is resolved from its name (no --type needed). Requires an ssh:// server.
Usage:
Options:
- --port <localport> - Local port to bind (default: the addon's own port). Use this if the default port is already in use.
Examples:
hop3 tunnel mydb # -> postgresql://...@127.0.0.1:5432/mydb
hop3 tunnel mydb --port 6543 # bind a different local port
hop3 tunnel mycache # -> redis://...@127.0.0.1:6379/0
hop3 ps¶
Show process count for an app.
Usage:
hop3 ps scale¶
Set the process count for an app.
Usage:
Example:
hop3 app run¶
Run a one-off command in the context of an app. hop3 run is a top-level alias. The app is read from the --app/-a flag (else resolved from context); everything that remains is the command line to execute.
Usage:
Options:
- --input <data> - Data to send to the command's stdin (non-interactive)
Examples:
# Run database migrations (app from context)
hop3 app run python manage.py migrate
# Run a command for an explicit app
hop3 app run --app myapp python manage.py shell
# One-off script
hop3 app run --app myapp node scripts/cleanup.js
hop3 app shell¶
Open an interactive shell in an app's runtime context. hop3 ssh is a top-level alias. The shell starts in the app's working tree with the environment its processes see (addon URLs, the [env] block, the toolchain on PATH), as the hop3 user. It is hop3 app run with a terminal attached: REPLs, tail -f, pagers and debuggers work. Exit the shell to end the session; its exit status becomes the command's.
This is a client-side command over SSH, not RPC: the CLI opens ssh -t to the configured ssh:// server and hands the terminal to the server. It needs an ssh:// server. With stdin redirected, no terminal is allocated and the shell runs what it is piped, then exits.
Usage:
Examples:
# Shell into the app resolved from context
hop3 app shell
# Shell into an explicit app
hop3 app shell --app myapp
# Scripted: no terminal, commands from stdin
echo 'flask db upgrade' | hop3 app shell --app myapp
hop3 app sbom¶
Generate a Software Bill of Materials (SBOM) for an app.
Usage:
Output: - CycloneDX format JSON - Lists all dependencies with versions - Security scanning metadata
Exit Codes¶
The Hop3 CLI uses the exit-code table defined in ADR 036 D16. Scripts can distinguish user error from server error from "user said no" without parsing messages.
| Code | Meaning |
|---|---|
0 |
Success (including empty results) |
1 |
Generic error (fallback) |
2 |
Usage / syntax error (invalid arguments, malformed flags) |
3 |
Resolution error (app or context not found) |
4 |
Authentication error (not logged in, token expired) |
5 |
Authorization error (forbidden, permission denied) |
6 |
Conflict (resource already exists, locked, in use) |
7 |
Network / server error (connection, timeout, 5xx) |
8 |
Deployment failure |
9 |
Plugin error |
10 |
Confirmation declined or non-tty blocked |
130 |
Interrupted (SIGINT / Ctrl-C) |
JSON output (--json) includes error.exit_code in the envelope so programmatic consumers don't have to mirror this mapping.
Script tip. Code 10 is your friend: use it to distinguish a user typing "no" at a confirmation prompt from an actual operation failure. Non-interactive scripts should pass --confirm=<name> or --yes to avoid it altogether, or --no-input to fail fast with an actionable message instead of hanging.
Machine-readable help¶
hop3 help --all --json emits every command as JSON instead of a page of prose,
so scripts, dashboards, and generated documentation never have to parse help
output:
hop3 help --all --json | jq -r '.[] | select(.t=="data") | .data.commands[] | select(.hidden|not) | "\(.name)\t\(.summary)"'
Each entry carries name, summary, usage, examples, aliases, and the
flags namespace, hidden, destructive and requires_auth. Hidden commands
are included and marked, so each consumer applies its own
rule.
Help itself also works before the CLI has a server: hop3 and hop3 help on a
fresh install print the built-in command list and how to connect, instead of a
configuration error.
Environment Variables¶
Configuration¶
HOP3_API_URL- Server URL (http://server or ssh://user@server)HOP3_API_TOKEN- API authentication token (overrides the token stored in the per-server credential store)HOP3_CONTEXT- Select a deploy environment / context (see Context Management)HOP3_VERBOSITY- Default verbosity level (0=quiet, 1=normal, 2=verbose, 3=debug)HOP3_APP- Sticky app name for the current shell session (app-resolution chain, ADR 036 D7)HOP3_NO_INPUT- When set to1, interactive prompts are refused with an actionable error. Set automatically when--no-inputis passed; can be set manually to propagate through subprocesses.
Security¶
HOP3_SECRET_KEY- Encryption key for credentials (server-side, required in production)HOP3_UNSAFE- Disable authentication (⚠️ NEVER use in production, test-only)
Development¶
HOP3_DEBUG- Enable debug loggingHOP3_HOST- Target server for the deploy/test tooling when--hostis omitted (ADR 052);HOP3_DEV_HOSTis a deprecated alias and is not read as a pytest test-target selector (ADR 043)
Tips and Best Practices¶
Security¶
- Never use
HOP3_UNSAFEin production - This completely disables authentication - Protect your token - The token lives in
~/.config/hop3-cli/credentials.toml(created0o600, parent dir0o700); keep that file readable only by you - Rotate tokens regularly - Use
auth logoutandauth loginto refresh - Backup HOP3_SECRET_KEY - Required to decrypt service credentials
- Use SSH connections - Preferred over HTTP for remote servers
Workflow¶
-
Always backup before destructive operations:
-
Use
--dry-runwhen available: -
Check status before and after operations:
-
Use
--jsonfor automation:
Confirmation Prompts¶
Destructive commands require confirmation:
- Type the resource name - For app destroy and addon destroy, type the resource name
- Type 'DELETE' - For backup destroy
- Skip with -y - Use --yes flag to auto-confirm (use carefully!)
Example:
$ hop3 app destroy --app myapp
WARNING: This will permanently delete the app 'myapp' and all its data.
Type the app name to confirm: myapp
✓ App 'myapp' destroyed successfully.
Getting Help¶
Command-specific Help¶
Documentation¶
- User Guide: Quickstart
- Backup Guide: Backup and Restore
- Migration Guide: CLI Migration
Community¶
- GitHub Issues: https://github.com/abilian/hop3/issues
- Documentation: https://docs.hop3.cloud
Last Updated: 2026-04-17 CLI Version: 0.5.0dev Server Version: 0.5.0dev v