2026-08-25 · Guide

Self-Hosting Rakazo: The Complete Setup Walkthrough

The reason people bounce off self-hosted bot runtimes is not the install. It is discovering, on day three, that the thing they installed is a service with a database, a job queue, and a container host, and that nobody agreed to own it.

So this walkthrough does two jobs. It gets you from empty directory to a working bot using the commands that are actually in the repository, and it tells you plainly what you have signed up for afterwards. Everything below was read from elie222/rakazo on 2026-08-25. Rakazo is in beta and moves quickly, so treat the repo as the source of truth and this page as the orientation.

Know what you are actually deploying before you clone anything

Rakazo's own self-hosting guide leads with the honest framing: the signed-in product is a long-running API, a Graphile Worker, Postgres, and a computer provider, and "it is not a static site" (self-hosting guide).

Those four, plus the web server that fronts them, all have to stay alive for a scheduled bot to fire:

PieceWhat it doesDefault port
APIServes the app, RPC, and health3100
WorkerRuns bot turns and scheduled routines through Graphilenone
Postgres 16Threads, runs, routines, memory, usage records5433 on loopback
Sandbox supervisorOwns the Docker socket and the bot computers7091
WebVite preview of the app5173

Those port numbers come from .env.example and the Compose file, and the setup prompt tells you to check all four are free before you start (SETUP_PROMPT.md).

Settle four decisions before you type the first command

The install is mostly typing. The afternoon goes on four choices, all easier to make now than after a bot has state.

DecisionOptionsWhat it actually costs youPick this unless you know better
Where the stack runsYour laptop, one VPS, an existing internal hostA laptop stops firing routines when it sleeps; a VPS is a machine you now patchLaptop to evaluate, one small VPS the moment a routine matters
Where bot computers runDocker on the same host, E2B, Daytona, Box, or the API host itselfDocker on the host means the graphical desktop competes with your database for RAMDocker to evaluate, E2B for anything public or multi-user
How much machine2 GB for a quiet box, 4 GB for API plus worker plus Postgres, 8 GB if Docker computers share itGuessing here means migrating a running deployment two months laterFollow the repo: a 2 vCPU and 4 GB VM with E2B owning the desktops
Who can registerOpen signups, or SIGNUP_ALLOWLISTThe first registered user becomes the deployment ownerAllowlist on, register yourself first, open it later if ever

Those sizes are the repo's own numbers: the self-hosting guide names a Hetzner CX22 at 2 vCPU and 4 GB as enough for API, worker and Postgres when E2B owns the desktops, and puts 8 GB only on a machine that also runs Docker computers.

The provider choice deserves one extra beat, because it is the one people revisit angrily. The guide calls Docker the default for local use and E2B the recommended choice for public or multi-user production, and it is direct about the option that looks convenient: a desktop provider running commands on the API host as you is documented as something not to enable on a public or shared service, with the note that macOS shows no permission dialog of its own for it. Choose once, deliberately, and write down why. The longer version of that decision lives in Rakazo sandbox options.

Match Node and pnpm to the manifest, not to your habit

The README asks for Node.js 22 or newer, pnpm 9, and Docker Desktop. Two of those need a caveat.

The package manifest pins packageManager to pnpm 9.15.0 (package.json), and the repository's own setup prompt is explicit about why that matters: do not silently use pnpm 10 or 11, because newer versions "can reject this lockfile or rewrite it". Use Corepack, or run repo commands through npx --yes pnpm@9.15.0 rather than installing a different global pnpm.

The same manifest declares an engines range of `^22.22.2 || ^24.15.0 ||

=26.0.0`, so a stale Node 22.11 will be rejected even though it looks like "Node 22". Check the effective versions before you blame the install.

Clone and configure the environment

The quick start is four lines, and they are the same four in the README and in CONTRIBUTING.md:

git clone https://github.com/elie222/rakazo.git
cd rakazo
cp .env.example .env
node -e "console.log(require('crypto').randomBytes(32).toString('hex'))"

Run that last line twice. You need two independent random values, one for BETTER_AUTH_SECRET and one for ENCRYPTION_KEY, and the guide is specific that they must be independent rather than the same string pasted twice. Do not reuse a value you have used anywhere else, do not commit .env, and do not paste either value into a chat window, an issue, or a log line.

Rakazo will not let you skip this quietly. The self-hosting guide states that it "refuses placeholder or missing secrets" outside development and test environments, so a production boot with the shipped example values fails rather than starting up insecure.

Learn what the rest of the environment file controls

You do not need most of .env.example to get running. Here is what each group is for, at the level of what it controls rather than what to put in it.

GroupVariablesWhy it exists
Identity and cryptoBETTER_AUTH_SECRET, ENCRYPTION_KEYSession signing and encryption of stored connector secrets
OriginsBETTER_AUTH_URL, WEB_ORIGIN, API_URLCookies and CORS follow these exactly
DatastoreDATABASE_URL, DATA_DIRPostgres, plus the directory holding bot homes and browser profiles
ComputeSANDBOX_PROVIDER, SANDBOX_SUPERVISOR_URL, SANDBOX_IDLE_MSWhere bot computers run and when they pause
Provider keysE2B_API_KEY, DAYTONA_API_KEY, BOX_API_KEYOnly the one matching your chosen provider
ModelOPENROUTER_API_KEY, PI_DEFAULT_PROVIDER, PI_DEFAULT_MODELA deployment-wide model, or leave blank and connect per user
IntegrationsCOMPOSIO_API_KEY, PIPEDREAM_CLIENT_ID and friendsOptional managed app catalogs
Access controlSIGNUPS_ENABLED, SIGNUP_ALLOWLISTWho can register on this deployment

Two defaults are worth knowing because they show up as behaviour rather than config. SANDBOX_IDLE_MS defaults to 600000, so a bot computer pauses after ten idle minutes and resumes on the next message. SANDBOX_COMMAND_TIMEOUT_MS defaults to 300000, so a shell command is killed after five minutes. Both are in .env.example with those values.

Bring up Postgres and build the computer image

docker compose --env-file .env -f infra/compose/docker-compose.yml up postgres -d
pnpm install
pnpm db:generate
pnpm db:migrate
pnpm sandbox:build
pnpm dev

pnpm sandbox:build is the step that surprises people. It resolves to docker build -t rakazo/computer:local infra/sandboxes/computer, and that image installs a graphical Linux desktop: Xvfb, x11vnc, fluxbox, xdotool, ImageMagick, noVNC, and Chromium on Debian bookworm-slim (Dockerfile). The setup prompt warns it "may take several minutes" on first run. It is not hung.

Check the health endpoint before you open the browser

With pnpm dev running, check the API before you touch the UI:

curl -s http://127.0.0.1:3100/health

The repo tells you exactly what a healthy local stack reports: ok: true, runtime pi, sandbox docker, jobs graphile, and realtime postgres. revision stays null unless you set GIT_SHA, and the Composio and Pipedream flags are true only when you configured those credentials.

Then open http://127.0.0.1:5173 and register. The first registered user becomes the deployment owner, so do that yourself before you send the URL to anyone. Connect a model, create a bot, and open the computer pane to confirm the Docker computer reaches running and renders its desktop.

If you would rather not drive this by hand, the repository ships SETUP_PROMPT.md, a prompt you paste into a coding agent. It is worth reading even if you install manually, because it encodes the safety rules the maintainers care about: never overwrite an existing .env, never print secrets, never kill unrelated containers to free a port.

Prove the install works with three checks that can fail

A green health endpoint means the processes started. It does not mean the thing you installed does its job, and that gap is where people declare victory early. Three checks close it, and each is allowed to fail.

The computer boots and answers. Open a bot's computer pane, wait for it to reach running, and ask for something that needs the sandbox: a directory listing, or a page loaded in the browser. This catches an image that built but will not start, which on Windows shows up as the CRLF hang below.

A routine fires while nobody is watching. Schedule one for fifteen minutes out and leave it alone. This is the only check that exercises the worker and the job queue together, and it is the failure people find three weeks late, because a routine that never fires produces no error anywhere.

The stack survives a restart. Reboot the host, or close the terminal running pnpm dev, and see what is still alive. A development install is a foreground process that dies with your shell, while the Compose services are declared to restart unless stopped. If you plan to rely on schedules, this check is the difference between a demo and a deployment.

Write down what each returned the first time. Six weeks later a baseline is how you tell "it was always like that" from "something changed".

Recognise the failures that eat the first afternoon

Almost every stalled install is one of these.

What you seeWhat it actually isWhat to do
The database is empty and yesterday's bots are gonepnpm compose:down resolves to down -v, which removes the Compose volumesStop with a command that has no -v. Keep compose:down for a deliberate reset
A lockfile error, or an install that fails without naming a versionpnpm 10 or 11 against a lockfile pinned to 9.15.0, or a Node 22 build below 22.22.2Use Corepack, or run repo commands through npx --yes pnpm@9.15.0
The computer pane hangs at boot, and the sandbox log mentions bash\rA Windows clone made with core.autocrlf=trueSet core.autocrlf false, renormalise the checkout, rebuild the sandbox image
A service will not bindSomething else holds 5433, 3100, 5173 or 7091Change the port in .env. Do not kill the other process to win the argument
Health is green and the first message failsNo model connectedConnect a model. The stack being healthy is not the same as usable
On Docker Desktop, the updater reports no checkout at allRAKAZO_DEPLOY_DIR was set to a path the daemon cannot seeUse the path the daemon sees. On Windows that is under /run/desktop/mnt/host/c

Five of them deserve more than a row.

The destructive stop. pnpm compose:down resolves to a Compose down -v (package.json), and that -v removes the Compose volumes, which means your Postgres data. The setup prompt calls this out and tells you to use a stop or down command without -v for a normal shutdown (SETUP_PROMPT.md). Learn this before you learn it the hard way.

Bad Node or pnpm. Covered above, and it presents as a lockfile error or a cryptic install failure rather than a version message.

CRLF on Windows. An older clone with core.autocrlf=true leaves the computer pane hung at boot with bash\r in the sandbox logs. The fix in the guide is git config core.autocrlf false, then git add --renormalize . && git checkout -- ., then rebuild the sandbox image.

Port collisions. 5433, 3100, 5173, and 7091. Resolve them by configuration, not by killing whatever else is on the port.

No model connected. The stack will be perfectly healthy and the first message will still fail. The setup prompt is explicit that a deployment with model setup deferred should not be called usable. Pick your model deliberately rather than by default, which is the subject of choosing a model for Rakazo.

Publish one service, and keep every other one unreachable

The local defaults are safe because nothing is reachable. That stops being true the moment you put this on a VPS, and three things need attention.

Postgres is published on loopback only at 127.0.0.1:5433. The self-hosting guide says directly: do not expose that port on a public VPS, change POSTGRES_PASSWORD, and keep Postgres on an internal network.

The Docker supervisor is deliberately not published, because access to it "is equivalent to control of the Docker host". Leave it on the internal Compose network where the shipped file puts it.

Origins and signups. Put TLS in front of the web port and set BETTER_AUTH_URL, WEB_ORIGIN, and API_URL to your public HTTPS origin, because cookies and CORS follow those values. Then keep SIGNUP_ALLOWLIST tight while the service is private.

For a real public deployment the repo ships infra/compose/docker-compose.prod.yml, which runs Caddy for automatic HTTPS and uses E2B for bot computers so the VM never exposes a Docker supervisor or browser containers at all. There is also an idempotent Ubuntu hardening script, sudo DEPLOY_USER=deploy bash infra/compose/harden-host.sh, which disables SSH passwords and root login, configures UFW and fail2ban, and enables unattended security updates. Its own warning is the important part: keep the provider console open until a fresh SSH login succeeds after the script reloads SSH.

One first-deploy trap is documented in bold in the guide. RAKAZO_IMAGE_TAG ships as local, a tag no registry serves, so you build the images rather than pulling them. Running a pull first fails outright with a registry denial, and the guide notes that earlier versions of that page told people to do exactly that.

Read the production Compose file as a map of your exposed surface. One service publishes host ports, everything else is reachable only from a Docker network, and the database network is marked internal.

ServicePublished on the hostReachable from
Caddy80 and 443, TCP and UDPThe internet. This is the whole front door
WebNothingCaddy, over the edge and app networks
API and workerNothingThe app and data networks, with all Linux capabilities dropped
PostgresNothingThe data network only, which is declared internal
UpdaterNothingA dedicated control network shared with the API, behind a bearer token

Treat that as a checklist: if your deployment publishes anything beyond 80 and 443, either you added it or a tutorial did.

Take a backup you have actually restored once

This is the part to read before you decide, not after.

Backups are yours. The repo provides ./scripts/backup.sh, which dumps Postgres and archives data/ into a timestamped folder, and ./scripts/restore.sh backups/<stamp> to reverse it. DATA_DIR matters more than it looks: the computer runtime doc says production deployments must put it on a Rakazo-owned persistent volume, encrypt it at rest, and include it in off-host backups, and notes that the current store keeps only the latest workspace rather than an immutable revision archive (computer runtime).

Read the local script before you trust it. If the data directory is not where it expects, the home archive step falls back to creating an archive from an empty file list, so the command still prints a success line and writes a homes.tgz with nothing in it. A backup that succeeds and contains no bot homes is the worst kind, because you will not find out until a restore.

The production script is a better animal, worth reading even if you only run the local one. infra/compose/backup-prod.sh takes a custom-format dump, tars the API container's data directory, then verifies both by listing the dump with pg_restore --list and the archive with tar -tzf before writing SHA256SUMS, tightening permissions, and pruning snapshots older than seven days. A shipped systemd service and timer run it nightly at 02:15 UTC with a randomised delay (infra/systemd).

Two things it does not do are now your job. It writes to a directory on the same machine, so a host failure takes the snapshots with it. And verifying that a dump is readable is not the same as verifying that a restore produces a working deployment. Do the real restore once, into a scratch database, before you need it.

Understand what an upgrade can and cannot undo

Upgrades are a command, not an automatic thing. On a source checkout you pull, rebuild with GIT_SHA=$(git rev-parse HEAD), run the database migration, and restart the API and worker. On Compose you rebuild or move the image tag. The API runs prisma migrate deploy before it serves, so a failed migration keeps health red rather than half-starting, and a failed recreate does not roll back on its own.

Production Compose deployments can add an updater sidecar, which exists for one structural reason: the API cannot update itself, because its image has no checkout and nothing inside the container would restart it. The sidecar sits on the private control network, publishes no host port, requires a bearer token on every route except health, and holds the Docker socket, which the guide notes is root-equivalent on the host. The guide says plainly that you can drop the service if you would rather not hand a container that capability.

Learn the rollback behaviour before you need it. Rollback redeploys the previously cached image from the local Docker daemon and never contacts the registry, which is deliberate and which means it fails closed if you pruned that image. Leave the previous application image alone until the current update has proved itself. Then the sentence that decides your upgrade discipline: database migrations are not reversed. Rolling back the image does not roll back the schema, so an upgrade is only as reversible as your backup.

Price the upkeep before you commit to it

Security is a subscription you pay in attention. Rakazo's policy supports security fixes on the current main branch and the latest beta release, with no bug bounty, and explicitly puts operator misconfiguration out of scope (SECURITY.md). That is a reasonable position for a beta project. It also means the exposed surface is your responsibility.

As a standing commitment rather than a feeling, the job looks like this.

TaskHow oftenWhat it costs if you skip it
Apply upstream updatesMonthly at minimumFixes land only on current main and the latest beta, so an old deployment drifts out of support rather than staying still
Verify a backup by restoring itQuarterlyYou discover the empty archive or the missing volume during the incident
Copy snapshots off the hostEvery backupA host failure takes the snapshots with it, because both scripts write locally
Check that routines still fireWeeklyA stopped worker is silent. Nothing outside your own monitoring reports it
Reread the approval rules listMonthlyOne "always allow this tool" click quietly outranks a category rule you set months ago
Rotate and re-check secretsOn staff changeConnector credentials sit encrypted in a database whose backups you now hold

None of that is exotic. It is the ordinary operating cost of an internal service, worth pricing up front because a bot runtime is easy to install on a Tuesday and easy to forget by the next month.

The honest case for not running this yourself

The strongest argument against this walkthrough is that you take on a service to gain a database you will rarely open. The hosted route costs a subscription and no attention. This one costs an afternoon and then a standing share of your month, and its failure mode is undramatic: a worker that stopped in March and routines nobody noticed had gone quiet.

That argument wins more often than self-hosting advocates admit. One person, three bots, no appetite for being an operator: take the hosted product.

Four situations flip it, and they are specific rather than ideological. Your bots need to reach something a hosted runtime cannot see, such as a database behind your own network. You need the record, because an audit trail you can query is a self-hosting property and the Grok Bot documentation still says an audit view of Bot actions does not exist yet. You have a residency or contractual constraint that makes "where does this run" someone else's question to ask you. Or your desktops are Linux, which the hosted product does not support: its FAQ lists macOS, Windows and iPhone on iOS 18 and later, and the docs answer the Linux desktop question with a flat no.

Price the alternative honestly while you decide. As of 21 August 2026 the cheapest paid route to Grok Bot is Cursor Pro+ at 60 dollars a month, with Cursor Ultra, the Cursor Teams plans and SuperGrok Plus at 100 dollars a month also carrying access, while Cursor's free and 20 dollar tiers do not. A small VM plus your own model spend is not obviously cheaper for one person, and is often clearly cheaper for a team. The full comparison is in Rakazo versus Grok Bot.

The boundary to set before the first real job

A fresh Rakazo install does not ask before acting. The action-confirmation settings say so in the UI copy: "Bots act without asking by default. Add an exception only when you want to review a type of action first" (ApprovalRulesSettings.tsx). Under that, only destination.write, delete_bot, and archive_bot always require approval, while shell, computer_act, and write_file are exempt (action-approval.ts).

So before the first bot does anything real, click both presets, "Ask before sending external email" and "Ask before purchases", and write the boundary into the charter as well. Every listing on botskills.sh declares one, because the line is what makes a bot safe to leave running. Standup Scribe posts only to your own DM, never a shared channel. PR Review Sentinel never merges, approves, or pushes. Here is a first bot worth using to shake down a fresh install:

Bot: Deploy Watch
Computer: Private
Model: your cheap default

What you do
- Every weekday at 09:00, open the deploy dashboard and the error tracker.
- Note failed deploys since yesterday and any new error signature.
- Write a five line summary into the thread.

Boundary
- You never re-run, roll back, or trigger a deploy. Reporting only.
- You never open a production console or run a shell command outside the
  workspace.
- If a credential prompt or a 2FA challenge appears, stop and ask me.

Why this one first
- It exercises the browser, the schedule, and the routine, and the worst
  possible failure is a wrong summary.

Start with a bot whose failure mode is an inaccurate paragraph. Once the schedule fires reliably for a week, move on to bots that touch things, following the same read-first pattern we use for the hosted runtime in the one-person company setup.

Keep reading: Grok Bot Setup Guide, Migrating a Grok Bot Setup to Rakazo Without Rewriting It, Bot Boundaries.

Frequently Asked Questions

What do I need installed before I can self host Rakazo?

Node.js 22 or newer, pnpm 9.15.0, and a running Docker daemon, plus free ports on 5433, 3100, 5173, and 7091. The engines field accepts 22.22.2 and above, 24.15 and above, or 26 and above, so an older Node 22 build will be rejected. The pnpm version matters more than it usually does, because the repository's own setup guidance warns that pnpm 10 or 11 can reject or rewrite the lockfile. Use Corepack or invoke pnpm 9.15.0 directly. Docker is not optional: it runs Postgres and builds the bot computer image.

How long does the first install take?

Plan an afternoon rather than ten minutes. The clone, install, and migrations are quick. The step that dominates is building the computer image, which installs a full graphical Linux desktop and Chromium and is documented as taking several minutes on first run. After that, the slow parts are decisions rather than downloads: which sandbox provider you want, which model you connect, and whether you are exposing this to anyone else. A second install on the same machine, with the image cached, is a matter of minutes.

Is it safe to put a self-hosted Rakazo on a public VPS?

It can be, but not with the local defaults. The Postgres port is published on loopback and the guide tells you not to expose it publicly, the sandbox supervisor holds the Docker socket and must stay on the internal network, and you need TLS plus matching origin variables or authentication will not behave. The repository ships a production Compose file with Caddy and a host-hardening script for Ubuntu, and it uses a remote sandbox provider so the VM never exposes browser containers. Keep the signup allowlist on until you mean to open registration.

What breaks if I stop maintaining it?

Slowly, then all at once. Scheduled routines stop firing when the worker or Postgres goes down, and nothing outside your own monitoring will tell you. Security fixes only land on the current main branch and the latest beta release, so an unpatched deployment drifts further behind every month. The data directory holding bot homes and browser profiles keeps only the latest workspace copy, so an unbacked-up host loses signed-in sessions and bot files together. Treat it like any other internal service: one named owner, backups you have actually restored once, and a monthly upgrade window.

Self-Hosting Rakazo: The Complete Setup Walkthrough | botskills.sh