AI Decision System
Replayable, offline-verifiable records for AI-assisted go/no-go decisions: experiment promotions, typed model answers and human-approved actions.
Deploy static sites, PocketBase backends, and Docker apps to your own server with a single command. Automatic HTTPS. Wildcard subdomains.
Developer toolsTypeScript · HTMLGitHub @plossonOnlineOwner unconfirmed
Have you tried it? One tap is enough. No sign-in needed.
Deploy static sites, PocketBase backends, and Docker apps to your own server — one command
Taken from the repository README
Loading the full README. The saved excerpt is shown below.
A self-hosted deployment platform for static websites and Docker containers with automatic HTTPS via Traefik.
Windows binaries are not shipped in releases yet.
Configure your client to connect to the siteio agent server:
This repo includes working examples you can deploy immediately:
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:Read the full README ↗A self-hosted deployment platform for static websites and Docker containers with automatic HTTPS via Traefik.
# 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.
Configure your client to connect to the siteio agent server:
siteio login --api-url https://api.yourdomain.com --api-key your-api-key
Or use a connection token:
siteio login --token <connection-token>
Create a fresh Hetzner VM from your laptop, install the agent, and log in:
siteio config set hetznerToken <hetzner-cloud-token>
siteio server create mybox --sslip --email [email protected] --identity ~/.ssh/id_ed25519
Without --sslip, a configured Cloudflare token enables an interactive zone picker
with Buy a new domain as the last option:
siteio config set cloudflareToken <cloudflare-api-token>
siteio server create mybox
# Or use an existing zone without prompts:
siteio server create mybox --domain yourdomain.com --email [email protected] --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 <identity>.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 <type> and --location <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.
Progress is saved in ~/.config/siteio/provisioned-servers/<name>.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.
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.
This repo includes working examples you can deploy immediately:
# 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:
https://hello.yourdomain.comhttps://nginx-demo.yourdomain.comhttps://docker-demo.yourdomain.comDeploy a folder as a website:
siteio sites deploy ./my-website -n blog
This deploys your files to https://blog.yourdomain.com.
Every site ships with a PocketBase backend at /api — auth, database, file
storage, and realtime, no server code required:
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
Deploy a basic Nginx container:
# Create the app
siteio apps create mysite --image nginx:alpine --port 80
# Deploy it
siteio apps deploy mysite
Available at https://mysite.yourdomain.com.
Build and deploy directly from a Git repository:
# 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:
# 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
A git app can redeploy itself. The agent checks the repository every 5 minutes; nothing has to change in the repository.
# 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
tag mode the tag itself is built, and an older tag never causes a downgrade.siteio apps deploy still builds the branch HEAD. Use it to deploy a pre-release.siteio apps info shows the error.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.Deploy a service from a monorepo by specifying the build context:
# 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
Deploy a Node.js application with configuration:
# 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:[email protected]: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
-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:
# 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:
$ 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.
Deploy PostgreSQL with a persistent volume:
# 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
Deploy Redis with a custom domain:
# 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
Deploy a complete application stack:
# 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:[email protected]: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:[email protected]:5432/app" \
-e REDIS_URL="redis://redis.yourdomain.com:6379" \
-r unless-stopped
siteio apps deploy worker
# 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
All commands support --json for scripting:
siteio --json sites list
siteio --json apps info myapp