Read this when you:
- pick
provider: morph; - configure Morph Cloud API auth, snapshots, or SSH gateway access;
- change
internal/providers/morph.
Morph Cloud is a direct-only Linux SSH-lease provider. Crabbox boots a Morph instance from a configured snapshot, tags it with Crabbox lease metadata, fetches the per-instance SSH private key from the Morph API, and connects through the shared SSH gateway host (ssh.cloud.morph.so by default). From there the lifecycle is the normal Crabbox SSH path: warmup, run, ssh, status, list, and stop.
#When to use
Use Morph when you already keep a reusable Morph snapshot and want normal Crabbox sync-and-run behavior over SSH without running your own broker or VM fleet. Choose AWS, Azure, GCP, or Hetzner instead when you need brokered spend controls, desktop/VNC, browser, or code-server surfaces.
#Capabilities
| Capability | Supported |
|---|---|
| OS targets | Linux only |
| SSH | Yes, through the Morph SSH gateway |
| Crabbox sync (rsync over SSH) | Yes |
| Actions hydration | Yes |
| Desktop / browser / code | No |
| Tailscale | No |
| Coordinator (broker) | No, Morph always runs direct |
--class, --type, non-Linux --target, and --tailscale are rejected for this provider.
Snapshots must provide bash, git, rsync, tar, and at least one of python3, python, or perl; warmup verifies these before accepting the instance.
#Auth
Keep the Morph API key in the environment or user config, never on the command line:
export MORPH_API_KEY=...
Crabbox lookup order:
CRABBOX_MORPH_API_KEYMORPH_API_KEYmorph.apiKeyin user config
The API key is used as Authorization: Bearer <token> against the Morph REST API. crabbox doctor --provider morph is read-only: it validates auth by listing instances and fetching the configured snapshot.
#Configuration
provider: morph
target: linux
morph:
apiUrl: https://cloud.morph.so
snapshot: snapshot_abc123
sshGatewayHost: ssh.cloud.morph.so
workRoot: /tmp/crabbox
deleteOnRelease: false
wakeOnSSH: true
Defaults:
apiUrl:https://cloud.morph.sosshGatewayHost:ssh.cloud.morph.soworkRoot:/tmp/crabboxdeleteOnRelease:falsewakeOnSSH:true
snapshot is required to create a new instance.
Flags:
--morph-api-url
--morph-snapshot
--morph-ssh-gateway-host
--morph-work-root
--morph-delete-on-release
--morph-wake-on-ssh
Environment overrides:
CRABBOX_MORPH_API_KEY (or MORPH_API_KEY)
CRABBOX_MORPH_API_URL
CRABBOX_MORPH_SNAPSHOT
CRABBOX_MORPH_SSH_GATEWAY_HOST
CRABBOX_MORPH_WORK_ROOT
CRABBOX_MORPH_DELETE_ON_RELEASE
CRABBOX_MORPH_WAKE_ON_SSH
#Commands
crabbox warmup --provider morph --morph-snapshot snapshot_abc123
crabbox run --provider morph --morph-snapshot snapshot_abc123 -- pnpm test
crabbox ssh --provider morph --id blue-lobster
crabbox status --provider morph --id blue-lobster --wait
crabbox list --provider morph
crabbox stop --provider morph blue-lobster
#Lifecycle
- Validate that the configured Morph snapshot exists.
- Boot a new instance from that snapshot.
- Write Crabbox metadata (
lease,slug,work_root, TTL labels, and - Set the provider-side TTL and optional
wake_on_sshpolicy. - Wait for the instance to become ready, fetch the Morph SSH private key, and
- Connect as SSH user
<instance-id>@<sshGatewayHost>:22and use the normal - On release, pause the instance by default. If
morph.deleteOnReleaseis
provider=morph) back onto the instance.
store it in Crabbox's normal per-lease key path.
Crabbox sync/run flow.
true, Crabbox deletes it instead.
#Gotchas
- Morph uses API-returned credentials per instance. If the private key is
- SSH host keys for the shared Morph gateway persist in
stoppauses by default. Paused instances can be reused later byrun,morph.snapshotis required for new leases. Existing leases can still be- The SSH hostname is the shared gateway, not the instance-specific hostname,
passphrase-protected, Crabbox decrypts it in memory with the returned password, stores the usable key under the normal per-lease path with mode 0600, and keeps it while the instance is paused for reuse. Deleting the instance removes the key. The password is not passed in command-line arguments or retained on disk.
~/.config/crabbox/morph/known_hosts (or the platform user config equivalent), so trust-on-first-use survives lease cleanup.
ssh, or status --wait; with wakeOnSSH=true, SSH can wake them through the gateway. The local claim and SSH key remain until the instance is deleted.
resolved by lease id, slug, or instance id.
so status shows the gateway target for SSH. Morph-reported networking remains available in morph_hostname, morph_external_ip, and morph_internal_ip labels when present.
#Live testing
Two opt-in entry points exercise the real Morph API. Neither runs in the default go test -race ./... or node --test scripts/*.test.js CI jobs; both are skipped or fail-fast when the API key is absent. Both force deleteOnRelease=true so test instances are deleted instead of retained.
The Morph API key is read from the same contract as runtime auth:
CRABBOX_MORPH_API_KEYMORPH_API_KEYmorph.apiKeyin the user config
#Go integration test
export CRABBOX_MORPH_API_KEY=...
export CRABBOX_LIVE_MORPH_SNAPSHOT=snapshot_xxx
go test -tags smoke -run TestLiveMorphAcquireResolveTouchReleaseLease \
-count=1 -v ./internal/providers/morph/
The test boots a real instance from the configured snapshot, exercises Acquire → Resolve → Touch → List → ReleaseLease, and unconditionally releases the lease on exit. It skips when CRABBOX_MORPH_API_KEY / MORPH_API_KEY is unset or when go test -short is set.
#Live provider smoke
export CRABBOX_MORPH_API_KEY=...
export CRABBOX_LIVE_MORPH_SNAPSHOT=snapshot_xxx
go build -trimpath -o bin/crabbox ./cmd/crabbox
CRABBOX_LIVE=1 CRABBOX_LIVE_COORDINATOR=0 CRABBOX_LIVE_PROVIDERS=morph \
scripts/live-smoke.sh
Optional knobs: CRABBOX_LIVE_MORPH_SLUG (default morph-smoke-<pid>), CRABBOX_LIVE_MORPH_TTL (default 15m), CRABBOX_LIVE_MORPH_IDLE_TIMEOUT (default 5m).
#Loading the key from a local file
Keep the key in a file outside the repository (for example ~/Desktop/morph-crabbox/api.env.md). The repository .gitignore blocks *.env and api.env* so accidental git add . cannot leak it:
export CRABBOX_MORPH_API_KEY=$(tr -d '[:space:]' < ~/path/to/api.env.md)
> The key file is intentionally outside the repo. Do not commit it, do not > copy it into crabbox.yaml, and do not echo it in CI logs. The PR diff > must never contain the key bytes — verify with > git diff origin/main..HEAD | grep -E 'morph_[A-Za-z0-9]{20,}' (should > return nothing).