Your VPS, served on a tray.
Quickstart · Maintainers · Issues
bash <(curl -sSL https://raw.githubusercontent.com/felipefontoura/bento/stable/boot.sh)Paste that on a fresh Ubuntu/Debian VPS, answer three questions (domain, admin email, public IP), and ~15 minutes later you have a hardened host, Traefik + Portainer with TLS, the apps you picked, and an HTML handoff report. Already know bento? Copy and go. Want context? Keep reading.
Prefer to drive it from Claude Code instead of the server console? Install the
bentoplugin and Claude does the whole flow over SSH — you never SSH in by hand or touch the bento menu. See Install with Claude Code. Ideal for beginners (works on Windows) and for consultants setting up a client's VPS: hand Claude an SSH-reachable host + a domain + an app list and pick up at the end with credentials + URLs.
Table of contents
A guided installer that turns a fresh VPS into a hardened, TLS-fronted Docker Swarm with the apps you want — in under 15 minutes, with one terminal command.
You answer three questions (domain, admin email, public IP), walk through three guided steps (harden, infra, apps), and finish with a single HTML report you can hand off to a client. Every secret is generated for you; nothing is hardcoded.
A one-time bootstrap captures BASE_DOMAIN, ADMIN_EMAIL, and your VPS public IP (auto-detected). Then three idempotent, re-runnable steps:
An embedded copy of the ubinkaze hardening script:
installs Docker, applies kernel sysctl + UFW (with limit ssh) + fail2ban + AppArmor + AIDE + auditd + chrony,
creates a docker user with your SSH keys, then initializes Docker Swarm and the network_public overlay. Requires one reboot.
No prompts. Deploys Traefik (Let's Encrypt + HTTPS redirect) and Portainer at portainer.<your-domain>, generates a strong Portainer admin password, displays it once.
Pick from a checklist. For each stack, bento:
- Prompts only for env vars without sensible defaults. Hostnames default to
<key>.<your-domain>; secrets are auto-generated; DB passwords are reused from the postgres stack. - Deploys via Portainer's API as a Git-backed stack so Portainer becomes the canonical source of truth for the running spec.
- Runs an optional
install.shfor post-deploy bootstrap (DB creation, migrations). - Prints the URL — you open and log in.
When Paperclip is in the deploy set, Step 3 offers an optional Authenticate AI providers step right after a successful deploy so the agent runtime can immediately call out to Claude / OpenAI Codex. The same menu is available any time from the main menu, or directly as bento-auth on the host. Full details in docs/reference/bento-auth.md.
Everything above — Bootstrap, Step 1, Step 2, Step 3, the post-hardening reboot,
the unattended env vars, the recovery when something stalls — is drudgery you
shouldn't have to do by hand if you "just want to use it." The bento plugin
for Claude Code drives all of it over SSH. You never log into the server console
or touch bento's menu: you talk to Claude, it does the rest and hands you back
URLs + credentials.
You need: Claude Code, an SSH key on the VPS, and wildcard DNS (see Prerequisites). Nothing is installed locally except Claude Code — Docker/Swarm/apt all run on the VPS.
Install the plugin once — type these inside Claude Code (not your shell), one at a time. They are two separate slash commands; run the first, wait for it to confirm, then run the second. Don't paste both lines together — that concatenates them into one broken command.
-
Add the marketplace:
/plugin marketplace add felipefontoura/bento -
Then install the plugin:
/plugin install bento@felipefontoura
Then just ask. The plugin adds these /bento:* skills.
Lifecycle — get the box and its apps running:
| Command | What Claude does over SSH |
|---|---|
/bento:install |
Fresh VPS → hardened host + Traefik/Portainer + your apps. Runs Step 1/2/3 unattended, rides through the reboot, recovers from known failures, reports URLs + Portainer login. |
/bento:deploy |
Add or redeploy apps on a server that already runs bento. |
/bento:update |
Pull the latest bento and redeploy your stacks. |
/bento:status |
Read-only health check (services, HTTPS, disk/memory). |
/bento:auth |
Register an AI-provider API key and propagate it to your stacks. |
Operate — day-2 work inside the deployed apps, through each one's API. You don't invoke these by name: Claude auto-loads the right one when you describe the task in plain language ("send a WhatsApp", "reply to a customer", "build a workflow"). Each discovers the host/credentials from bento state — it never hardcodes your instance.
| Command | Operate… | Talk to it like |
|---|---|---|
/bento:paperclip |
Paperclip agent orchestration — agents, instruction bundles, skills, board issues. | "create an agent / import skills / clean up the board" |
/bento:hermes |
Hermes agent gateway — chat over the OpenAI-compatible API, run the CLI, wire MCP servers. | "talk to my agent / give it the youtube tools" |
/bento:n8n |
n8n workflows, driven through the n8n-mcp tools (schemas + validation baked in). | "build / fix / run a workflow" |
/bento:evolution-api |
Evolution API — WhatsApp instances, QR pairing, send messages, webhooks. | "connect my WhatsApp / send a message" |
/bento:chatwoot |
Chatwoot support desk — conversations, replies, contacts, inboxes. | "reply to a customer / list open chats" |
/bento:typebot |
Typebot chatbots — start/continue chats, publish, read results (builder vs viewer). | "start a bot chat / get results" |
/bento:plunk |
Plunk transactional email — send, track events, manage contacts (AWS SES behind it). | "send an email / track an event" |
/bento:metamcp |
MetaMCP gateway — group MCP servers into namespaces + endpoints, mint keys. | "add an MCP server / get my tools endpoint" |
Example: "/bento:install on root@198.51.100.42, domain example.com, apps n8n
and chatwoot" → Claude takes it from there. Later: "send a WhatsApp to +55…
saying the order shipped" → Claude loads /bento:evolution-api on its own.
Windows — the only local requirement is a working ssh
Docker and everything else run on the VPS, so locally you just need Claude Code
ssh. The lowest-friction path is WSL2, where the commands are identical to macOS/Linux:
wsl --install # PowerShell as admin, then reboot# inside the Ubuntu/WSL terminal:
ssh-keygen -t ed25519 # paste the .pub into Hetzner
curl -fsSL https://claude.ai/install.sh | bash
claude # then: /plugin marketplace add … / /plugin install …Native Windows works too (irm https://claude.ai/install.ps1 | iex, plus Git
for Windows for the Bash tool); if ssh complains about key permissions, run
icacls "$env:USERPROFILE\.ssh\id_ed25519" /inheritance:r /grant:r "$env:USERNAME:F"`.
Each stack is a directory at stacks/<category>/<key>/ with compose.yml, manifest.json, and optionally install.sh. Adding a new stack is documented in CLAUDE.md.
| Category | Stack | What it is |
|---|---|---|
| infra | Traefik | Reverse proxy + Let's Encrypt |
| infra | Portainer | Stack manager UI |
| db | PostgreSQL | Each app creates its own database in install.sh |
| db | Redis | In-memory cache |
| app | Chatwoot | Customer support platform |
| app | CLI Proxy API | OpenAI-compatible proxy in front of CLI providers |
| app | Crawl4AI | Headless web crawler/extractor (internal-only). Optional outbound proxy to dodge datacenter-IP anti-bot blocks — see docs/reference/crawl4ai-proxy.md |
| app | Evolution API | WhatsApp gateway |
| app | Hermes | Seeds a shared volume with Hermes Agent so Paperclip's hermes_local adapter can exec the CLI locally (overlay-only, idle sleep, no gateway) |
| app | MetaMCP | MCP aggregator/gateway — unify multiple MCP servers (stdio + HTTP) behind one endpoint, with a web admin UI |
| app | n8n | Workflow automation |
| app | n8n MCP | MCP server for n8n |
| app | Openclaw | Personal AI assistant with a web Control UI — sign in with your ChatGPT/Claude subscription, chat, connect a Telegram bot, no terminal. OpenAI-compatible API kept for overlay consumers |
| app | Paperclip | AI agent orchestration (Claude Code, Codex, OpenCode, Hermes via hermes-bin volume) |
| app | Plunk | Open-source email platform |
| app | RabbitMQ | Message broker |
| app | Typebot | Chatbot builder |
| Partner | When | Plan | Link |
|---|---|---|---|
| Hetzner (primary) | EU/US users — bento is smoke-tested against it every release | CX22, latest Ubuntu LTS | hetzner.cloud |
| Hostinger (secondary) | Brazil-based users — BRL billing, low BR latency | KVM 2+, latest Ubuntu LTS | hostinger.com/br/smartdev |
Affiliate disclosure (read once, applies to both)
Both links above are affiliate referrals. Signing up through them gives bento a small commission that funds new stacks and bug fixes — there is no premium price for you and no functional difference from a direct signup. If you'd rather not contribute, just visit hetzner.com or hostinger.com directly and the installer works identically. Same goes for any apt-based VPS (DigitalOcean, OVH, Vultr, your own metal).
You need a wildcard A record before Step 2, or Let's Encrypt fails on first boot:
| Type | Name | Value |
|---|---|---|
| A | *.mydomain.com |
<your VPS IP> |
(bento only uses subdomains. If you already have a website at the bare mydomain.com, leave its existing A/CNAME alone — the wildcard above won't touch it.)
Open the DNS records page on Cloudflare → — Cloudflare prompts you to pick the account + zone, then drops you straight onto the records page. Cloudflare is the recommended DNS host (free tier, fast); any provider works.
Verify before Step 2:
dig +short A portainer.mydomain.com
# should print your VPS IP| Layer | What it does | Setup |
|---|---|---|
| Hetzner Cloud Firewall | Edge filter; optional | Manual, in Hetzner panel |
| UFW + fail2ban | Default-deny inbound, limit ssh, allow 80/443/ICMP |
Automatic during Step 1 |
Recommended Hetzner Cloud Firewall ruleset
In Firewalls → Create Firewall → Apply to your server:
| Direction | Source | Port | Protocol | Why |
|---|---|---|---|---|
| Inbound | Your home IP | 22 | TCP | SSH — or leave open and let ufw limit + fail2ban handle brute-force |
| Inbound | 0.0.0.0/0 |
80 | TCP | Let's Encrypt HTTP-01 + HTTPS redirect |
| Inbound | 0.0.0.0/0 |
443 | TCP | HTTPS |
| Inbound | 0.0.0.0/0 |
any | ICMP | ping debugging |
| Outbound | 0.0.0.0/0 |
all | all | Default |
If you lock SSH to your home IP and your IP changes (mobile, ISP renewal), use Hetzner's web console to recover. For starter setups, leaving SSH open with ufw limit + fail2ban is a reasonable trade-off.
When Step 3 finishes — or any time, from the Report menu — bento writes a self-contained HTML file with the VPS overview, Traefik + Portainer access, and every deployed stack's URL and resolved env vars. Secrets are masked by default with click-to-reveal; print to PDF auto-reveals everything for offline handoff.
~/.local/share/bento/reports/handoff-<timestamp>.html # chmod 600
Move it off the VPS:
scp user@vps:~/.local/share/bento/reports/handoff-*.html .The report carries live credentials. Treat it like a password vault: deliver over an encrypted channel (1Password, Bitwarden Send, encrypted email), rotate if it ever leaks.
Same split as Helm + kubectl. bento owns the declarative state; Portainer owns day-to-day operations.
| Concern | bento | Portainer |
|---|---|---|
| Declarative state (what should run, with which envs) | owner | viewer |
| First deploy + git-backed updates | owner (via API) | executor |
| Logs, restart, scale, exec | redirect | owner |
Stacks created outside bento (no BENTO_MANAGED label) |
ignored | full owner |
Every bento-deployed stack carries BENTO_MANAGED=true + its source commit, so bento can spot drift and offer to reconcile during Update.
Re-running the curl|bash command always re-clones the latest boot.sh. Or, from the menu, pick Update to:
- Pull the latest bento code locally (
git fetch + reset --hard). - Re-deploy any stack whose
compose.ymlormanifest.jsonchanged since the last deploy (POST /api/stacks/<id>/git/redeploy).
| Path | Mode | Purpose |
|---|---|---|
~/.config/bento/state.json |
600 | Domain, email, IP, generated secrets, deployed-ref per stack |
~/.config/bento/portainer.json |
600 | Portainer admin credentials |
~/.local/state/bento/logs/ |
700 | Hardening + install logs |
~/.local/share/bento/reports/ |
700 | Handoff HTML reports |
The state schema is versioned and migrated automatically across bento updates.
git clone --branch stable https://github.com/felipefontoura/bento ~/.local/share/bento
cd ~/.local/share/bento
bash install.sh- Latest Ubuntu LTS, Debian, or any apt-based distro
rootor a non-root user withsudo— running directly asrooton a fresh VPS is fine- 1+ GB RAM, 5+ GB free disk
- Public IPv4
- Wildcard DNS pointing to that IPv4
Adding a stack, changing conventions, or extending the installer? Read CLAUDE.md — the canonical maintainer guide. It covers the Bento ↔ Portainer ownership model, the manifest schema, env resolution order, code style for shell + YAML + JSON, and a step-by-step recipe for adding new application stacks (with n8n called out as the gold-standard quality bar).
.claude/skills/contribute-stack/ is a Claude Code skill (invoked /contribute-stack) that automates the new-stack scaffold for AI-assisted contributions. It guards on being inside a repo clone, so it never fires for end users who only installed the bento operator plugin.
PRs welcome. Open an issue first for anything beyond a small fix.
Distributed under the MIT License.
