# siteio A self-hosted deployment platform for static websites and Docker containers with automatic HTTPS via Traefik. ## Installation ```bash # macOS / Linux (official installer) curl -LsSf https://houlahop.com/siteio/install | sh # Or from this repo with Bun bun install bun run src/cli.ts --help ``` Windows binaries are not shipped in releases yet. ## Quick Start Configure your client to connect to the siteio agent server: ```bash siteio login --api-url https://api.yourdomain.com --api-key your-api-key ``` Or use a connection token: ```bash siteio login --token ``` --- ## Provision a server Create a fresh Hetzner VM from your laptop, install the agent, and log in: ```bash siteio config set hetznerToken siteio server create mybox --sslip --email you@yourdomain.com --identity ~/.ssh/id_ed25519 ``` Without `--sslip`, a configured Cloudflare token enables an interactive zone picker with **Buy a new domain** as the last option: ```bash siteio config set cloudflareToken siteio server create mybox # Or use an existing zone without prompts: siteio server create mybox --domain yourdomain.com --email you@yourdomain.com --identity ~/.ssh/id_ed25519 ``` `HETZNER_TOKEN` (also `HCLOUD_TOKEN`) and `CLOUDFLARE_API_TOKEN` (also `CLOUDFLARE_TOKEN`) override saved credentials. Config files are written with mode 0600 and tokens are masked by `config get` and `config set`, including JSON output. The SSH public key must be next to its private key as `.pub`; multiple keys in `~/.ssh` trigger a picker unless `--identity` is given. By default the command resolves the cheapest currently available x86 VM type from Hetzner's API in Falkenstein (`fsn1`) and uses the `docker-ce` app image. Override with `--type ` and `--location `. The VM is billable and its firewall allows inbound TCP 22, 80 and 443. Custom wildcard DNS points directly to the VM. Existing DNS with different settings is rejected; matching pre-existing records are preserved during destruction. Cloudflare requires Zone Read and DNS Edit permissions; buying also needs Registrar Write and Account Settings Read for account discovery. Verify these permissions in the dashboard when creating the token; the Registrar template key still needs live dashboard validation. Purchases display the domain, registration price and renewal price and require an explicit, non-refundable purchase confirmation. Set up the account's payment method, default registrant contact and Domain Registration Agreement in the Cloudflare dashboard first. API-supported TLDs are checked at runtime; premium domains are not purchased. Auto-renew defaults to off. See the [Cloudflare Registrar workflow](https://developers.cloudflare.com/registrar/registrar-api/). Progress is saved in `~/.config/siteio/provisioned-servers/.json` after each step. Run `siteio server create mybox` again to resume. Omitted names default to `siteio`. Keep the saved files: they identify resources owned by this command. A pending or uncertain purchase is checked by domain status and is never submitted again automatically. If Cloudflare reports a failed, blocked or action-required registration, resolve it in the dashboard before resuming. DNS propagation, VM boot and certificate issuance may require another run after a timeout. ```bash siteio server destroy mybox # For scripts, explicitly confirm destruction: siteio server destroy mybox --yes ``` Destroy removes the VM, firewall, owned wildcard DNS and local login entry. It keeps the domain registration and uploaded SSH key. Failed cleanup can be resumed with the same command. `server` manages cloud infrastructure; `agent` manages software on a box. --- ## Try It Out This repo includes working examples you can deploy immediately: ```bash # Deploy the example static site siteio sites deploy examples/static -n hello # Deploy a pre-built Docker image siteio apps create nginx-demo --image nginx:alpine --port 80 siteio apps deploy nginx-demo # Build and deploy from Git (this repo!) siteio apps create docker-demo \ --git https://github.com/plosson/siteio \ --context examples/docker \ --port 3000 siteio apps deploy docker-demo ``` After deployment: - Static site: `https://hello.yourdomain.com` - Nginx demo: `https://nginx-demo.yourdomain.com` - Docker demo: `https://docker-demo.yourdomain.com` --- ## Site Examples ### Example 1: Deploy a Website Deploy a folder as a website: ```bash siteio sites deploy ./my-website -n blog ``` This deploys your files to `https://blog.yourdomain.com`. ### Example 2: A Site with Auth, Database, and Storage Every site ships with a PocketBase backend at `/api` — auth, database, file storage, and realtime, no server code required: ```bash siteio sites init ./todo # scaffold: index.html + starter schema + AI guide siteio sites dev # run locally with the backend, no Docker siteio sites deploy # ship it siteio sites admin # backend dashboard URL + credentials ``` --- ## Docker Container Examples ### Example 1: Simple Nginx Web Server Deploy a basic Nginx container: ```bash # Create the app siteio apps create mysite --image nginx:alpine --port 80 # Deploy it siteio apps deploy mysite ``` Available at `https://mysite.yourdomain.com`. ### Example 2: Deploy from Git Repository Build and deploy directly from a Git repository: ```bash # Create app from GitHub repo siteio apps create myapi --git https://github.com/user/myapi --port 3000 # Deploy (clones repo, builds image, runs container) siteio apps deploy myapi ``` With custom options: ```bash # Specify branch and Dockerfile siteio apps create myapi --git https://github.com/user/myapi \ --branch develop \ --dockerfile Dockerfile.prod \ --port 3000 # Force rebuild without Docker cache siteio apps deploy myapi --no-cache ``` #### Auto-deploy A git app can redeploy itself. The agent checks the repository every 5 minutes; nothing has to change in the repository. ```bash # Redeploy on each new commit of the tracked branch siteio apps set myapi --auto-deploy commit # Redeploy on each higher release tag (vMAJOR.MINOR.PATCH only; v1.2.3-rc1 is ignored) siteio apps set myapi --auto-deploy tag # Stop siteio apps set myapi --auto-deploy off ``` - In `tag` mode the tag itself is built, and an older tag never causes a downgrade. - A manual `siteio apps deploy` still builds the branch HEAD. Use it to deploy a pre-release. - A failed build leaves the running version up and is not retried until a newer commit or tag appears. `siteio apps info` shows the error. - Compose apps are not supported. - Set the interval with `SITEIO_AUTODEPLOY_INTERVAL` (seconds, default 300, minimum 60) on the agent, or with `autoDeployInterval` in the agent's `agent-config.json`. The env var wins when set and not empty. ### Example 3: Monorepo Deployment Deploy a service from a monorepo by specifying the build context: ```bash # Deploy backend service from monorepo siteio apps create backend --git https://github.com/user/monorepo \ --context services/backend \ --dockerfile Dockerfile \ --port 8080 siteio apps deploy backend ``` ### Example 4: Node.js API with Environment Variables Deploy a Node.js application with configuration: ```bash # Create the app siteio apps create nodeapi --image node:20-alpine --port 3000 # Configure environment variables siteio apps set nodeapi \ -e NODE_ENV=production \ -e DATABASE_URL="postgres://user:pass@db.example.com:5432/mydb" # Configure secrets (stored encrypted, never displayed again) siteio apps set nodeapi --secret JWT_SECRET="your-secret-key" # Set restart policy siteio apps set nodeapi -r unless-stopped # Deploy siteio apps deploy nodeapi ``` ### Secret environment variables `-e KEY=value` is plain configuration: it is stored as-is and printed back by `siteio apps info`. For anything sensitive — a passphrase, a database URL, an API token, a signing key — use `--secret` instead: ```bash # Inline siteio apps set myapp --secret VAULT_PASSPHRASE="correct horse battery staple" # From a file, keeping the value out of shell history and the process list siteio apps set myapp --secret-file VAULT_PASSPHRASE=./passphrase.txt # From stdin, same idea pass show vault | siteio apps set myapp --secret-stdin VAULT_PASSPHRASE # A whole env file at once — every key in it becomes a secret siteio apps set myapp --secret ./secrets.env ``` The agent stops sending the value back once a key is marked secret, so the CLI has nothing to print: ```console $ siteio apps info myapp Environment: NODE_ENV=production VAULT_PASSPHRASE=•••••••• (secret) ``` There is no flag to reveal it — a secret is write-only once set. To change one, set it again; to remove it, `siteio apps unset myapp -e VAULT_PASSPHRASE`. Restart the app for either to take effect. What this is for: stopping the CLI from being the easy way to read every secret of every app, and keeping values off your screen during a share or a demo. It is not protection against someone with access to the server — the agent stores the value as it stores any other env var, and the container needs it in its environment, so `docker inspect` on the host still shows it. ### Example 5: PostgreSQL Database with Persistent Storage Deploy PostgreSQL with a persistent volume: ```bash # Create the app siteio apps create postgres --image postgres:16-alpine --port 5432 # Configure with environment and volume siteio apps set postgres \ -e POSTGRES_USER=myuser \ -e POSTGRES_PASSWORD=mysecretpassword \ -e POSTGRES_DB=myapp \ -v pgdata:/var/lib/postgresql/data \ -r always # Deploy siteio apps deploy postgres ``` ### Example 6: Redis Cache with Custom Domain Deploy Redis with a custom domain: ```bash # Create the app siteio apps create redis --image redis:7-alpine --port 6379 # Configure volume and custom domain siteio apps set redis \ -v redis-data:/data \ -d cache.example.com \ -r unless-stopped # Deploy siteio apps deploy redis ``` ### Example 7: Full-Stack Application (Multiple Services) Deploy a complete application stack: ```bash # Backend API siteio apps create backend --image myregistry/backend:latest --port 8080 siteio apps set backend \ -e NODE_ENV=production \ -e DATABASE_URL="postgres://user:pass@postgres.yourdomain.com:5432/app" \ -e REDIS_URL="redis://redis.yourdomain.com:6379" \ -v uploads:/app/uploads \ -d api.example.com \ -r unless-stopped siteio apps deploy backend # Background worker siteio apps create worker --image myregistry/worker:latest --port 9000 siteio apps set worker \ -e NODE_ENV=production \ -e DATABASE_URL="postgres://user:pass@postgres.yourdomain.com:5432/app" \ -e REDIS_URL="redis://redis.yourdomain.com:6379" \ -r unless-stopped siteio apps deploy worker ``` --- ## Common Commands ```bash # List all sites siteio sites list # List all apps siteio apps list # View app logs siteio apps logs myapp --tail 100 # Stop an app siteio apps stop myapp # Restart an app siteio apps restart myapp # Bring a stopped app back (same image, no rebuild) siteio apps start myapp # Take a site offline (404) and bring it back siteio sites stop mysite siteio sites start mysite # Remove an app siteio apps rm myapp # Remove a site siteio sites rm mysite # Open the in-site live editor: chat with an AI editor on the site itself and # watch changes land in place (requires an LLM credential configured on the agent) siteio sites edit mysite --open # Revoke all outstanding editor links for a site siteio sites edit mysite --revoke ``` ## JSON Output All commands support `--json` for scripting: ```bash siteio --json sites list siteio --json apps info myapp ```