# ifhost — Impossible Hosting > CLI + API for deploying websites and backend services to the cloud. > API base URL: https://host.impossibuild.ai > > Docs: > CLI Guide: https://host.impossibuild.ai/docs > Advanced: https://host.impossibuild.ai/docs/advanced > llms.txt: https://host.impossibuild.ai/llms.txt ## Install CLI installer="$(mktemp)" curl --fail --show-error --location --proto '=https' --proto-redir '=https' --tlsv1.2 https://host.impossibuild.ai/install --output "$installer" sed -n '1,240p' "$installer" # inspect downloaded code before executing it sh "$installer" rm -f "$installer" # Windows (PowerShell): download to a new local file, inspect, then execute it $Installer = Join-Path ([IO.Path]::GetTempPath()) ("ifhost-install-" + [guid]::NewGuid() + ".ps1") Invoke-WebRequest https://host.impossibuild.ai/install.ps1 -OutFile $Installer Get-Content $Installer & $Installer Remove-Item $Installer ## Auth ifhost login # Interactive sign-in (opens browser) printf '%s' "$IFHOST_TOKEN" | ifhost login --token - # Read token from stdin ifhost login --from-file /run/secrets/ifhost-token # Or a local secret file IMPOSSIBLE_API_TOKEN # CI secret env var; overrides credentials file Credentials stored at: ~/.impossible/credentials.json Token format: imp_ + 64 hex chars. Server stores SHA-256 hash only. ## Deploy (the core flow) Every deploy starts with `ifhost init` — generates `impossible.toml` with machine specs (app name, port, memory, cpus, autostop, min-machines, storage). `ifhost deploy` errors if there is no `impossible.toml` in the current directory. Agents must pick specs explicitly, not rely on silent defaults. ifhost init --app my-app --port 3000 --memory 512 ifhost deploy # reads impossible.toml Agent decision tree: ifhost deploy boots a generic shell VM. Drive install via `machines exec` / `machines write`, or `machines console` for interactive setup (see "Runner + console" section below). Run the project's own install steps — no build, no stack detection. Required init flags (agents: pass all of these, no prompts): --app, --port, --memory, --cpus, --autostop, --min-machines, --storage Optional: --cpu-kind (shared | performance), --cmd (startup override) The CLI auto-creates the app on first deploy. Apps run on a single machine (multi-machine scaling is on the roadmap). Use `ifhost machines --app ` to list the machine's state. ## CLI Commands ### Account (no --app needed) ifhost login # Interactive sign-in (opens browser) printf '%s' "$IFHOST_TOKEN" | ifhost login --token - # Read token from stdin ifhost login --switch # Switch between stored accounts ifhost logout # Remove active account ifhost status # Login email, plan, all apps, CLI version ifhost init --app --port N --memory N --cpus N --autostop=BOOL --min-machines N --storage MODE # Create impossible.toml — REQUIRED before first deploy ifhost deploy # Boot a shell VM to install your app step-by-step ifhost apply --app # Push config (memory/cpus/env/secrets) to running machines without rebuilding ifhost regions # List available regions ifhost version # Show CLI version; auto-updates hourly by default ifhost update # Explicit independently signed update ifhost skill sync # Verify/cache latest SKILL.md + RUNBOOK.md ifhost billing plan # Show current plan ifhost billing usage # Show resource usage ### Machine management (requires --app) ifhost machines --app # List machines ifhost machines start --app # Start all machines ifhost machines stop --app # Stop all (no cost while stopped) ifhost machines restart --app # Restart with fresh env/secrets ifhost machines logs --app # Live tail (runs until Ctrl+C) ifhost machines logs --app --since 1h # Last hour, then exit ifhost machines logs --app --lines 50 # Last 50 lines, then exit ifhost machines logs --app --grep "ERROR" # Filter (works in both modes) ifhost machines logs --app --level error --lines 100 # Error-level only, historical ifhost machines logs --app --json # Structured JSON per line ifhost machines env set KEY=VALUE --app # Set env var (add --restart to apply) ifhost machines env list --app # List env vars ifhost machines secrets set KEY=@env:NAME --app # Secret source: @env, @file, or @stdin ifhost machines secrets list --app # List secret keys (values hidden) ifhost machines volumes create --mount /data --size 5 --app # Create volume ifhost machines volumes list --app # List volumes ifhost machines volumes rm --app # Delete volume ifhost machines domains add example.com --app # Add custom domain + TLS ifhost machines domains list --app # List domains ifhost machines exec --app -- [args...] # Run command (picks first running machine) ifhost machines exec --app --machine -- # Target a specific machine ifhost machines destroy --app --yes-irreversible # Delete app and all resources (irreversible) Global flags: --json (structured output on all commands) ## Status (overview across all your apps) `ifhost status` groups your account's apps by project and lists each project's URL, status, region, and current machine IDs (running + standby). Agents should run `status` first — the returned machine IDs are inputs for `machines exec --machine ` and `machines console start --app `. ifhost status # human-readable list ifhost status --json # structured — pipe to jq for scripts ## Exec (run commands inside the running container) ifhost machines exec --app my-app -- ls / ifhost machines exec --app my-app -- env ifhost machines exec --app my-app -- cat /app/config.json ifhost machines exec --app my-app -- python manage.py migrate ifhost machines exec --app my-app -- sh -c "df -h && free -m" Target a specific machine: ifhost machines --app my-app # list machine IDs with name/state ifhost machines exec --app my-app --machine 32d41... -- # pin exec to one Without `--machine`, exec picks the first running machine. Runs inside the machine. Available tools depend on the image: - node:20 → has node, npm, sh, ls, cat, curl - python:3.12 → has python, pip, sh, ls, cat, curl - alpine → has sh, ls, cat (no bash, no curl unless installed) - nginx:alpine → has sh, ls, cat, nginx - distroless/scratch → NO shell, exec will fail Limitations: - ~10-minute timeout per command (enforced server-side, cannot be raised). For anything longer, use the fire-and-poll pattern: launch with `nohup > /tmp/log 2>&1 < /dev/null &`, then poll with further exec calls until done. - No interactivity (no vim, htop, tmux) — output returned as text - No stdin — cannot pipe input - Machine must be running (start with: ifhost machines start --app ) - Runs as the container's default user (usually root unless the image sets a non-root USER) Use cases for agents: - Debug: check env vars, inspect files, test connectivity - Migrate: run database migrations after deploy - Config: read/verify runtime configuration - Health: check disk usage, memory, running processes ## Runner + console (for projects with multi-step / interactive setup) Use when the upstream project's install is too complex or interactive to capture up front (`hermes setup`-style wizards, multi-step CLIs, custom auth flows, agent-managed setup). Flow: 1. ifhost init --app --memory 1024 --min-machines 1 --storage local # Create impossible.toml. storage=local gives a persistent /data. # min-machines=1 so the daemon stays up; autostop=true would kill idle bots. 2. ifhost machines secrets set K=@env:K K2=@file:/secure/k2 --app # Inject creds without literal argv values 3. ifhost deploy --app # Boots a generic shell VM 4. ifhost deploy --app # ONE more time after secrets — re-applies init config 5. ifhost machines console start --app -- bash # Returns { session_id, machine_id } 6. Drive the install through console input/output (see below) The runner is a generic Debian shell environment with tmux preinstalled. ### Driving an install through the console SESSION="ifhost-01..." # from `console start` ifhost machines console input --app "$SESSION" "" ifhost machines console input --app "$SESSION" --key Enter ifhost --json machines console output --app "$SESSION" --lines 80 ifhost machines console end --app "$SESSION" # sessions persist until ended (no auto-cleanup) Echo a unique marker after each long command and poll `console output` until the marker (with rc) appears, so you know success/failure WITHOUT timing out: cmd='apt-get install -y --no-install-recommends curl git python3 ...; echo __PHASE1_DONE__rc=$?' # send cmd, send Enter # poll until output matches: __PHASE1_DONE__rc=(\d+) For background daemons (gateways, message bus loops), launch inside a NAMED tmux session inside the container so the process survives `console` disconnect: tmux new-session -d -s app "cd /data/src && PYTHONUNBUFFERED=1 exec ./bin/run 2>&1 | tee /data/app.log" Always set `PYTHONUNBUFFERED=1` (or equivalent) for piped Python output — otherwise log files stay empty for minutes due to block-buffering. ### Common gotchas - `ifhost machines exec` holds a synchronous connection open for the command's ~10-minute budget, but for anything more than a minute or two (apt installs, npm, pip with native compiles), use fire-and-poll or the console + polling pattern above instead of holding a synchronous exec open. - `shared-cpu-1x` is throttled. A "kitchen sink" apt install (build-essential + ffmpeg + nodejs) takes 5-10 min. Slim down to ONLY the packages you actually need — for a Telegram bot: `curl ca-certificates git python3 python3-venv python3-pip tmux` (~50 MB, ~1 min). Skip ffmpeg unless TTS, skip build-essential unless pip needs to compile, skip nodejs unless the project uses it. For genuine heavy installs, consider bumping `[resources] cpus = 2` or `cpu_kind = "performance"`. Both are capped by the account's plan (see Pricing tiers), so check before you raise them. - Setting secrets may drop the runner's init config. Always run `ifhost deploy` ONE more time after the secrets are set, then start the console. Verify with `ifhost machines exec --app -- env` that the secret keys are present in the container env. - `ifhost deploy` after `ifhost machines destroy` works on a fresh app — the previous machine + volume are gone, fresh `/data` is auto-created from `storage = "local"`. Same toml; no extra flags. - For projects that ship optional dependency extras (`pip install .[all]`): install only the extras you NEED (e.g. `pip install python-telegram-bot` not the whole `[messaging]` extra). Saves ~5 min per redeploy. Look at the project's pyproject.toml `[project.optional-dependencies]` to find minimal extra names. - If the project has a `gateway`/daemon command that writes a PID file: use ` --replace` on restart, or run ` stop --all` first, otherwise the new instance refuses to start while the old PID is alive. - For projects driven by `OPENROUTER_API_KEY` / model providers: set `model.default` and `model.provider` explicitly via the project's config CLI before first message. An unset model often fails with confusing errors (e.g. OpenRouter 404 "No endpoints found for ."). Don't rely on "auto-detect" defaults. ### Verifying the bot/daemon is alive ifhost machines exec --app -- ps -ef | grep ifhost machines exec --app -- tail -50 /data/.log ifhost machines exec --app -- cat /data/logs/agent.log # if the project has its own log dir For Telegram-style bots, the canonical "it's working" signal in logs: INFO ✓ telegram connected INFO Gateway running with 1 platform(s) If logs show empty or just a banner: the daemon may be alive but Python output is buffered. Re-launch with `PYTHONUNBUFFERED=1` and check `/data/logs/*.log` (most projects also write structured logs there). ## Config File (optional) File: impossible.toml (in project root) app = "my-app" region = "iad" [service] internal_port = 8080 autostop = true min_machines = 0 [resources] cpu_kind = "shared" # "shared" always; "performance" on Pro and Team cpus = 1 # 1, 2, 4, 8, capped per plan (see Pricing tiers) memory_mb = 256 # 256, 512, 1024, 2048, ... [[volumes]] name = "data" size_gb = 5 mount_path = "/data" [env] NODE_ENV = "production" ## REST API All endpoints require: Authorization: Bearer imp_xxx Content-Type: application/json ### Auth POST /auth/clerk/exchange { token } → Clerk browser session becomes an ifhost web session; new account: 403 { terms_required, signup_token } POST /auth/clerk/accept { signup_token, terms_accepted } → create a terms-stamped account and web session POST /auth/device/start { device_name } → { device_code, user_code, verification_url, interval, expires_in } GET /auth/device/lookup ?user_code=XXXX-XXXX → { user_code, device_name, status } for the approval page POST /auth/device/approve { user_code, clerk_token, action? } → approve or deny a CLI device grant; new account: 403 { terms_required, signup_token } POST /auth/device/poll { device_code } → 202 pending; 200 { status: approved|denied, token?, email? }; approved token is one-use GET /auth/google/cli-config (legacy compatibility only) → Desktop client_id for pre-device-flow CLIs POST /auth/google/cli (legacy compatibility only) → Google loopback + PKCE exchange GET /auth/me → { id, email } POST /auth/token { "name": "..." } → { token, name } GET /auth/tokens → { tokens: [...] } DELETE /auth/tokens/{id} → { revoked: id } ### Apps GET /apps → { apps: [...] } POST /apps { "name": "x", "region": "iad" } → { id, name, url } GET /apps/{name} → { id, name, status, url, cpu_kind, cpus, memory_mb, ... } PATCH /apps/{name} { "memory_mb": 512, ... } → { updated: [...] } DELETE /apps/{name} → { deleted: name } ### Deploy POST /apps/{name}/runner-deploy (no body) → { id, status, url, machine_id } GET /apps/{name}/deployments → { deployments: [...] } ### Machines POST /apps/{name}/stop → { stopped: N } POST /apps/{name}/start → { started: N } POST /apps/{name}/restart → { restarted: N } GET /apps/{name}/machines → { machines: [...] } POST /apps/{name}/exec { "cmd": ["ls", "/data"] } → { stdout, stderr, exit_code } ### Env & Secrets GET /apps/{name}/env → { vars: { K: V, ... } } PUT /apps/{name}/env { "vars": { "K": "V" } } → { set: N, restarted: bool } GET /apps/{name}/secrets → { keys: ["K1", "K2"] } PUT /apps/{name}/secrets { "secrets": { "K": "V" } } → { set: N, restarted: bool } ### Volumes GET /apps/{name}/volumes → { volumes: [...] } POST /apps/{name}/volumes { "name": "data", "size_gb": 5, "mount_path": "/data" } → { ... } DELETE /apps/{name}/volumes/{vol} → { removed: vol } ### Domains GET /apps/{name}/domains → { domains: [...] } POST /apps/{name}/domains { "hostname": "example.com" } → { hostname, tls_status, cname_target } DELETE /apps/{name}/domains/{h} → { removed: hostname } ### Logs GET /apps/{name}/logs ?format=json&since=1h&no-follow=true → text/event-stream ### Health GET /apps/{name}/health → { app, status, healthy, machines_total, machines_running, uptime_seconds, http: { total_requests, error_rate, avg_response_ms, p99_response_ms }, machines: [...] } ### CLI Version (no auth) GET /cli/version → { build_id: "20260416-024938" } ### Regions (no auth) GET /regions → { regions: [{ code, name, area }] } ### Billing GET /billing/plans (no auth) → { plans: [{ id, name, price_cents, price_display, limits: { apps, apps_unlimited, pool_memory_mb, pool_volume_gb, bandwidth_gb_mo, cpus_per_app, cpu_kind, compute_hours_mo, compute_hours_mo_unlimited } }] } GET /billing/plan → { plan_tier, limits, usage (incl. estimated_cost_cents) } GET /billing/usage → { period, entries: [{ app, type, quantity, unit }], totals: { : { total, unit, display } } } GET /apps/{name}/usage → { ... per-app usage } PUT /billing/alert { "max_cents": 2000 } → { max_cents, display } GET /billing/alert → { max_cents, current_cents, exceeded, display } DELETE /billing/alert → { status: "removed" } ## Storage modes (--storage flag on deploy) ifhost deploy --app my-app # stateless (default — no persistent storage) ifhost deploy --app my-app --storage local # local /data volume, single machine Two modes: ### empty (default) — stateless - No persistent disk. Container filesystem is ephemeral; survives restarts but resets on redeploy. - Use this for HTTP services, workers, or anything that keeps state in a managed DB (Supabase, Neon, Upstash, Turso) instead of locally. ### local — for embedded state (SQLite, file caches) - 1 GB volume mounted at /data, persists across redeploys (grow with `volumes extend`) - ⚠ Volumes are per-machine — one machine, one disk - ⚠ Region-locked at first deploy - Use only when the app must write to disk and you accept single-machine operation. For Postgres/MySQL/Redis at scale, use a managed DB instead. Object/cloud storage (auto-provisioned S3 buckets) was REMOVED. If your app needs blob storage, sign up directly with Tigris/S3/R2/B2 and pass credentials via --secret. ## Database recommendations (DO NOT use volumes for databases beyond SQLite) For databases, use a MANAGED service: Supabase — managed Postgres, free tier, dashboard included Neon — serverless Postgres, scales to zero, free tier Turso — managed SQLite at the edge, distributed reads PlanetScale — managed MySQL, branching workflow Connect via DATABASE_URL env var: ifhost deploy --secret DATABASE_URL=@env:DATABASE_URL These scale independently of your app. Your app stays stateless — the recommended architecture for ifhost apps. ### Subscription GET /subscription → { plan_tier, status, ... } POST /subscription/checkout { "plan": "pro" } → { checkout_url } # Hosted page: card + crypto (USDC). Card is # temporarily unavailable — crypto works today. POST /subscription/cancel → { cancelled: true } GET /subscription/invoices → { invoices: [...] } ### Pay-as-you-go top-up (x402) GET /subscription/topup/preview ?plan=

→ { deposit_address, plan, monthly_price_usdc, rate_days_per_dollar, minimum_credit_usdc, bound_wallets, expires_at_current } GET /subscription/topup/recent ?limit=N → { credits: [...] } GET /subscription/topup/x402 ?amount_usd=N (x402 payment header) → 402 { accepts: [...] } or { status: "credited", days_granted, expires_at } # ifhost billing topup : HTTP 402 challenge → # signed EIP-3009 payment, or a manual USDC # transfer to the deposit address from a bound wallet. ## Architecture ifhost CLI (Go binary) → ifhost API Server (Go) → Cloud VMs │ durable managed datastore - CLI: Go + cobra. Single binary, no runtime deps. - API: Go + chi. Handles auth, deploy orchestration, secrets encryption. - DB: durable managed datastore. Secrets encrypted AES-256-GCM with per-app HKDF keys. - Infra: lightweight VMs — per-second billing, scale-to-zero, 30+ regions. ## Env var handling - Non-sensitive (NODE_ENV, PORT): ifhost machines env set → injected into container env - Sensitive (DB_URL, API_KEY): `ifhost machines secrets set K=@env:K` → values never returned by the API, only keys listable - Both can also be set during deploy: `ifhost deploy --env K=V --secret SECRET=@file:/secure/value` - Setting env/secrets is staged by default; pass `--restart` or restart once after all changes - Never put literals in impossible.toml or argv. Use `@env`, `@file`, or `@stdin`; a tracked `[secrets]` config is refused and never uploaded. ## Pricing tiers Free: $0/mo, 1 app, 1 GB RAM pool, 1 shared CPU, 100 GB traffic Hobby: $15/mo, 2 apps, 2 GB RAM pool, 2 shared CPUs, 100 GB traffic Pro: $49/mo, 8 apps, 8 GB RAM pool, 4 performance CPUs, 500 GB traffic Team: $149/mo, 20 apps, 24 GB RAM pool, 8 performance CPUs, 1024 GB traffic