[Blog](https://deploywithox.com/blog) [Guides](https://deploywithox.com/blog#c-guides) # How to self-host LobeHub (AI chat) on your own VPS Run LobeHub v2.2.19, an AI chat workspace, on your own Ubuntu server with ox: Node.js, Postgres with pg_search, Redis and RustFS, from the real config. Saurav Sharma Oct 17, 2026 11 min read On this page - [What you need](https://deploywithox.com/blog/self-host-lobehub#what-you-need) - [Install LobeHub on Ubuntu without Docker](https://deploywithox.com/blog/self-host-lobehub#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-lobehub#config) - [Postgres, Redis and the file store](https://deploywithox.com/blog/self-host-lobehub#services) - [Updates and backups](https://deploywithox.com/blog/self-host-lobehub#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-lobehub#faq) To self-host LobeHub you need an Ubuntu server, a fork of the LobeHub repo with one `ox.toml` file and one small script added, and 17 variables. In our run the first deploy took about 12 minutes, and ox ran Postgres with the pg_search extension, Redis and a file store beside the app. **How we tested** On Oct 17, 2026, ox deployed LobeHub v2.2.19 from commit [`234e0fc7`](https://github.com/lobehub/lobehub/commit/234e0fc753dbbe7cdc7b2de0dcb493bf482d3f46) on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory. The run then redeployed it, backed it up and rebooted the server. Then a separate test agent, one that did not write the config, signed in, sent a chat message and saw the chat kept after a reload (the server had no AI model key, so the reply was an error), and checked the app's services and its backup. [LobeHub](https://github.com/lobehub/lobehub) is a browser workspace where you chat with language models, build agents, and upload files for them to read. It is a Next.js app. Postgres holds its data, Redis holds sessions and caches, and an S3-compatible store holds uploaded files. The code is under the [LobeHub Community License](https://github.com/lobehub/lobehub/blob/234e0fc753dbbe7cdc7b2de0dcb493bf482d3f46/LICENSE), which is Apache License 2.0 with a commercial license required to distribute a derivative work. ox deployed LobeHub v2.2.19 (commit 234e0fc7) on Oct 17, 2026 on a fresh DigitalOcean server with 8 vCPUs and 16 GB of memory; the first deploy took 12 minutes and a redeploy took 9 minutes. - **First deploy:** 12 minutes, from adding the project until the app passed the run's first checks. - **Redeploy of the same commit:** 9 minutes, until the app passed the checks again. - **Server:** DigitalOcean, 8 vCPUs and 16 GB of memory (`s-8vcpu-16gb`), with this app alone on it. Every one of the 85 apps we deployed ran on this one size, so it was not picked for this app. - **Memory in use after the reboot:** 1593 MB of 15986 MB, for the whole server: Ubuntu, the app, its services, Caddy and the ox agent. On a server with no apps, ox's tests on real servers hold the agent to 40 MB or less and Caddy to 50 MB or less. - **Services ox runs for it:** `paradedb`, `redis`. - **Background processes:** `rustfs`, each its own systemd unit beside the web process. - **Scheduled jobs:** `bot-gateway`. - **Source:** [github.com/lobehub/lobehub](https://github.com/lobehub/lobehub) at v2.2.19, commit [`234e0fc7`](https://github.com/lobehub/lobehub/commit/234e0fc753dbbe7cdc7b2de0dcb493bf482d3f46). The LobeHub sign-in page The LobeHub home page greeting the test user after signing up ## What you need - An Ubuntu LTS server. The run used the size in the facts above, with LobeHub alone on it. - A GitHub account to fork `lobehub/lobehub`, and an ox account, which you open by signing in with GitHub. - A key from an AI model provider, if you want answers in the chat. The run had none. Mail was not tested. ## Install LobeHub on Ubuntu without Docker In the ox dashboard, press Add a server and run the command it shows, as root, on the new machine. It installs only the ox agent and Caddy. Fork `lobehub/lobehub` and put release v2.2.19, the commit the facts list, on your fork's default branch. Add the `ox.toml` shown below at the repo root and the script `scripts/ox/s3-bucket.mjs` at its path, then push. Write `lobehub.env` with the 13 values from the variables section that are not secrets. LobeHub's sample file has one live key, `OPENAI_API_KEY`, so ox asks for it. The run gave it no value, and a line `unset OPENAI_API_KEY` marks it not needed. The other four values are secrets that ox makes for you. With the [ox CLI](https://deploywithox.com/docs/cli) signed in, run: Shell ```bash ox new /lobehub ox review lobehub --generate AUTH_SECRET --generate KEY_VAULTS_SECRET --generate JWKS_KEY --generate RUSTFS_SECRET_KEY --from-file lobehub.env --wait ``` The review saves the values, makes the four secrets and starts the first deploy, and `--wait` prints the run until it ends. The browser has the same two steps; see the [quickstart](https://deploywithox.com/docs/quickstart). When the deploy is green, open the address ox shows. LobeHub opens its sign-in page. Follow Sign up now, register with a name, an email address and a password, and finish the short onboarding. The home page then offers the Lobe AI agent. The test agent sent Lobe AI a message and got an error about the missing model key, as expected. After a reload the topic and the message were still listed. The run used the preview address, so a domain of your own was not tested; the [domains page](https://deploywithox.com/docs/operate/domains) covers how ox adds one. Uploading a file was not tested either. ## The ox.toml, line by line TOML ```toml # LobeHub (lobehub/lobehub), built and run the way its Dockerfile and # docker-compose/deploy/docker-compose.yml do, without the containers: # Next.js on node, ParadeDB's postgres (pg_search beside pgvector), redis, # and RustFS for the files people upload. packages = ["curl"] [app] # The Dockerfile's runtime ENV, and INTERNAL_APP_URL as compose sets it # (the app calling itself without the round trip through the proxy). # MIDDLEWARE_REWRITE_THROUGH_LOCAL makes the proxy rewrite /signin and the # other SPA routes to http://127.0.0.1:$PORT; without it Next rewrites to # https://localhost:, which the plain http server cannot answer (500). # NODE_ENV stays out of the variables: the build's pnpm install would then # leave out the devDependencies the build needs. start = 'export NODE_ENV=production MIDDLEWARE_REWRITE_THROUGH_LOCAL=1 INTERNAL_APP_URL="http://127.0.0.1:$PORT" && node scripts/ox/s3-bucket.mjs && exec next start -H 127.0.0.1 -p "$PORT"' [build] # The Dockerfile's build: `npm run build:docker` after dropping the desktop # routes, less its DOCKER=true, which makes a standalone server that # `next start` does not serve. commands = [ "rm -rf src/app/desktop 'src/app/(backend)/trpc/desktop'", "pnpm run build:spa", "pnpm run build:spa:mobile", "pnpm run build:spa:auth", "pnpm run build:spa:workbench", "pnpm run build:spa:share", "WORKBENCH_REQUIRED=1 SHARE_REQUIRED=1 pnpm run build:spa:copy", "next build", ] # startServer.js runs scripts/migrateServerDB before the server starts. migrate = "pnpm run db:migrate" [workers] # compose's rustfs. The browser uploads to a presigned URL and reads files # through one, so it reaches the bucket on the app's own address (path # style, S3_ENDPOINT=${PUBLIC_URL}); the server talks to it on RUSTFS_URL. rustfs = { run = 'export RUSTFS_ADDRESS="127.0.0.1:$PORT" RUSTFS_CONSOLE_ENABLE=false RUSTFS_ACCESS_KEY="$S3_ACCESS_KEY_ID" RUSTFS_SECRET_KEY="$S3_SECRET_ACCESS_KEY" && exec rustfs server s3-data', port = true, paths = ["/lobe"], health = "/health" } [cron] # startServer.js starts the bot gateway once the server is up; the call is # a no-op while it runs, and brings back the bots' connections after a # restart. The secret goes in on stdin, never on the command line. bot-gateway = { schedule = "*/5 * * * *", run = 'printf "Authorization: Bearer %s\n" "$KEY_VAULTS_SECRET" | curl -fsS -o /dev/null -H @- -X POST "$APP_URL/api/agent/gateway/start"' } [tools] "github:rustfs/rustfs" = "1.0.2" [storage] keep = ["s3-data"] [services] redis = {} # compose's paradedb image: PostgreSQL with pg_search, which migration 0090 # creates and 0093's bm25 indexes use, and pgvector, which pg_search # requires. pg_search must be preloaded, so the built-in postgres cannot # carry it: this is Ubuntu's PostgreSQL 18 with ParadeDB's release build # of pg_search unpacked into the data dir (the .deb's Depends are in # packages) and found through PostgreSQL 18's extension_control_path. [services.paradedb] packages = ["postgresql-18", "postgresql-18-pgvector", "libopenblas0", "curl"] memory = "1G" provides = { DATABASE_URL = "postgresql://app:${password}@${host}:${port}/postgres" } run = ''' v=0.26.2 case "$(dpkg --print-architecture)" in amd64) arch=amd64 sum=c11b0c53d70abfff3764369c6821bd09e6ffe7d711f97b3932e28999c1ceb29c ;; arm64) arch=arm64 sum=478710240fd34f1991e267696022e3bf9edfc461910814b4957b3c1a4c6e4bf8 ;; *) echo "ParadeDB builds pg_search for amd64 and arm64 only" >&2; exit 1 ;; esac ext="$SERVICE_DATA_DIR/pg_search-$v" if [ ! -e "$ext/.ready" ]; then rm -rf "$ext" "$ext.deb" curl -fsSL -o "$ext.deb" "https://github.com/paradedb/paradedb/releases/download/v$v/postgresql-18-pg-search_$v-1PARADEDB-resolute_$arch.deb" echo "$sum $ext.deb" | sha256sum -c --quiet dpkg-deb -x "$ext.deb" "$ext" rm "$ext.deb" # Load it by name, from dynamic_library_path, not from postgres's own lib dir. sed -i 's|\$libdir/pg_search|pg_search|' "$ext/usr/share/postgresql/18/extension/pg_search.control" touch "$ext/.ready" fi bin=/usr/lib/postgresql/18/bin data="$SERVICE_DATA_DIR/pgdata" if [ ! -d "$data" ]; then rm -rf "$data.new" "$bin/initdb" -D "$data.new" -U app -E UTF8 --locale=C.UTF-8 -A scram-sha-256 --pwfile=<(printf '%s\n' "$SERVICE_PASSWORD") mv "$data.new" "$data" fi exec "$bin/postgres" -D "$data" -p "$PORT" -c listen_addresses=127.0.0.1 -c unix_socket_directories= \ -c shared_preload_libraries=pg_search \ -c dynamic_library_path="\$libdir:$ext/usr/lib/postgresql/18/lib" \ -c extension_control_path="\$system:$ext/usr/share/postgresql/18" ''' ``` The config follows LobeHub's Dockerfile and compose file, without the containers. - `packages` adds curl for the cron job. ox runs pnpm at the version `package.json` names, and Node 24, which the Dockerfile pins, so there is no `install` line. - `start` exports three settings. `NODE_ENV=production` sits here, not in the variables, because a variable would reach the build, where pnpm would skip packages the build needs. `MIDDLEWARE_REWRITE_THROUGH_LOCAL=1`, from the Dockerfile, makes the app's proxy rewrite `/signin` and the other single-page routes to the app's own loopback address over plain HTTP, which the server can answer. `INTERNAL_APP_URL` lets the app call itself on loopback. - The script makes the file bucket if it is missing, then `next start` serves the app on loopback behind Caddy. - The build `commands` follow the Dockerfile's `build:docker` step: drop the desktop app's routes, build the five single-page apps, copy them into place, run `next build`. Its `DOCKER=true` is left out, since it makes a standalone server that `next start` does not serve. - `migrate` runs LobeHub's `db:migrate` before traffic switches, as its Docker start script does before the server. - The `rustfs` worker is the compose file's file store. It listens on loopback, takes its keys from the variables, keeps files in `s3-data`, and must pass its `health` check before the app switches. `paths = ["/lobe"]` sends `/lobe` on the app's address to it, because the browser uploads and reads files through presigned URLs. One address needs no CORS rules or public bucket policy. - The `bot-gateway` cron job runs every 5 minutes. It posts to the route LobeHub's start script calls once, which starts the gateway that connects agents to chat apps, and it brings the connections back after a restart. The secret goes in on stdin, not on the command line. No chat app was connected in the run. - `[tools]` installs RustFS 1.0.2, `[storage]` keeps `s3-data` across deploys, and `redis = {}` is a private Redis. - `[services.paradedb]` is a Postgres of the repo's own. LobeHub's migrations create the pgvector and pg_search extensions, and Postgres must preload pg_search, which ox's built-in Postgres cannot. So the entry runs Ubuntu's PostgreSQL 18 with pgvector and ParadeDB's pg_search 0.26.2 release package, caps its memory at 1G, and provides `DATABASE_URL`. - The `run` script downloads that package once, checks its SHA-256, and unpacks it into the data folder. The first start runs `initdb`, then Postgres starts on loopback with pg_search preloaded. `scripts/ox/s3-bucket.mjs` Code ``` // Makes the bucket LobeHub keeps files in when it is missing, as compose's // rustfs-init does (`rc mb lobe --ignore-existing`). On a deploy ox starts // the rustfs worker, and waits for its health, before the app; after a // reboot the app may start first, and then this fails and systemd starts // the app again a few seconds later. import { CreateBucketCommand, HeadBucketCommand, S3Client } from '@aws-sdk/client-s3'; const Bucket = process.env.S3_BUCKET; const s3 = new S3Client({ credentials: { accessKeyId: process.env.S3_ACCESS_KEY_ID, secretAccessKey: process.env.S3_SECRET_ACCESS_KEY, }, endpoint: process.env.S3_INTERNAL_ENDPOINT, forcePathStyle: true, region: process.env.S3_REGION || 'us-east-1', }); try { await s3.send(new HeadBucketCommand({ Bucket })); } catch (err) { if (err?.$metadata?.httpStatusCode !== 404) throw err; await s3.send(new CreateBucketCommand({ Bucket })); console.log(`made the S3 bucket ${Bucket}`); } ``` The script checks the bucket named in `S3_BUCKET` with LobeHub's own S3 client, and makes it only when the store says it is missing. After a reboot the app can start before RustFS. The script then fails, and systemd starts the app again a few seconds later. Shell ```bash APP_URL AUTH_SECRET KEY_VAULTS_SECRET JWKS_KEY RUSTFS_SECRET_KEY DATABASE_DRIVER LLM_VISION_IMAGE_USE_BASE64 REDIS_PREFIX REDIS_TLS S3_ACCESS_KEY S3_ACCESS_KEY_ID S3_SECRET_ACCESS_KEY S3_BUCKET S3_ENABLE_PATH_STYLE S3_ENDPOINT S3_INTERNAL_ENDPOINT S3_SET_ACL ``` `AUTH_SECRET` is the session encryption key, and `KEY_VAULTS_SECRET` encrypts the provider keys people save. `JWKS_KEY` signs internal tokens and must be an RS256 key set. `RUSTFS_SECRET_KEY` is the file store's secret key. The `--generate` flags have ox make all four, each in the format it needs, so none of them passes through your terminal. `APP_URL` and `S3_ENDPOINT` are `${PUBLIC_URL}`, a reference ox fills in with the project's address; see [variables](https://deploywithox.com/docs/operate/variables). `S3_INTERNAL_ENDPOINT` is `${RUSTFS_URL}`, the worker's own address. `S3_ACCESS_KEY` and `S3_ACCESS_KEY_ID` hold one access key name, and `S3_SECRET_ACCESS_KEY` refers to `RUSTFS_SECRET_KEY`. `DATABASE_DRIVER=node` picks the plain Postgres driver, since LobeHub's default is Neon's serverless one. The other six, such as `S3_BUCKET` and `REDIS_PREFIX`, take the compose file's settings. ## Postgres, Redis and the file store ox ran all three for this project alone. Postgres and Redis show on the Services tab. The first page load after a deploy can take several seconds while Next.js warms up. The compose file's gateway, SearXNG and Elasticsearch were not run. ## Updates and backups To move to a newer release, merge its tag into your fork and push, or run `ox deploy lobehub --wait`. ox builds the new release beside the running one, migrates, and switches once it answers. The run redeployed the same commit, and an upgrade to a later version was not tested. Backups need no setup. After Back up now, the run's server held two: the `paradedb` data folder, about 89 MB, and a copy of `s3-data`, about 6 KB. `ox backups lobehub` lists them. ox's [configuration docs](https://deploywithox.com/docs/config) say a service of this kind stops for the moment its folder is archived. A restore was not tested. The [backups page](https://deploywithox.com/docs/operate/backups) covers copies in an S3 bucket. ## Frequently asked questions ### Do I need Docker to self-host LobeHub? No. LobeHub's docs lead with Docker Compose, which starts the app, Postgres, Redis, RustFS and more as containers. ox builds the same app from your fork and runs the same pieces as system services. An update is a push to your fork or one deploy command. ### Which database does LobeHub need? A Postgres with the pg_search and pgvector extensions. LobeHub's migrations create pg_search and its search indexes, so a plain Postgres fails on a fresh database. LobeHub's docs name ParadeDB's image for this. The run used Ubuntu's PostgreSQL 18 with both, started by a service entry in the config. ### How do I make the first LobeHub account? There is no admin variable. Open the project's address, follow Sign up now, and register with a name, an email address and a password. Email verification is off by default, so the account works at once, and anyone who reaches the page can sign up. AUTH_ALLOWED_EMAILS limits sign-ups; the run left it unset. ### How much memory does self-hosted LobeHub use? The facts above show what the run's whole server used after a reboot, counting LobeHub, Postgres, Redis, RustFS, Caddy and the ox agent. Memory during a long chat or a file upload was not measured. ox takes any server from 1 GB up and says so when memory is why an app stopped. ### Why does my first LobeHub chat message fail? The server has no model provider key, and LobeHub's README says a key is required to chat. The run set none, so its reply was an API key error, though the message was saved. LobeHub reads a key from a variable such as OPENAI_API_KEY or from the app's provider settings. Adding one was not tested. New to running apps on your own server? [How ox self-hosts your apps](https://deploywithox.com/self-hosting) shows what ox sets up on your server. On this page - [What you need](https://deploywithox.com/blog/self-host-lobehub#what-you-need) - [Install LobeHub on Ubuntu without Docker](https://deploywithox.com/blog/self-host-lobehub#steps) - [The ox.toml, line by line](https://deploywithox.com/blog/self-host-lobehub#config) - [Postgres, Redis and the file store](https://deploywithox.com/blog/self-host-lobehub#services) - [Updates and backups](https://deploywithox.com/blog/self-host-lobehub#updates) - [Frequently asked questions](https://deploywithox.com/blog/self-host-lobehub#faq) - [Do I need Docker to self-host LobeHub?](https://deploywithox.com/blog/self-host-lobehub#faq-docker) - [Which database does LobeHub need?](https://deploywithox.com/blog/self-host-lobehub#faq-database) - [How do I make the first LobeHub account?](https://deploywithox.com/blog/self-host-lobehub#faq-account) - [How much memory does self-hosted LobeHub use?](https://deploywithox.com/blog/self-host-lobehub#faq-memory) - [Why does my first LobeHub chat message fail?](https://deploywithox.com/blog/self-host-lobehub#faq-key) Written by Saurav Sharma Builds ox. Writes about running apps on servers you own, without Docker and without a platform bill. [Older post How to self-host Logto on your own VPS without Docker](https://deploywithox.com/blog/self-host-logto) [Newer post How to self-host Litestar Fullstack on your own VPS](https://deploywithox.com/blog/self-host-litestar-fullstack) ## Keep reading - Guides [Self-Hosted Open-Source Apps Deployed with ox: All 85](https://deploywithox.com/blog/self-hosted-open-source-apps-deployed-with-ox) ox deployed 85 open-source apps from pinned commits on fresh servers, with no Docker. The stacks, services, deploy times and a table of every app. Saurav Sharma Oct 17, 2026 16 min read - Guides [How to self-host AFFiNE on your own VPS without Docker](https://deploywithox.com/blog/self-host-affine) Run AFFiNE v0.27.4 on your own Ubuntu server with ox: Postgres with pgvector, Redis, a Rust and Node build, and an admin made from two variables. Saurav Sharma Oct 17, 2026 12 min read - Guides [How to self-host Akaunting on your own VPS without Docker](https://deploywithox.com/blog/self-host-akaunting) Run Akaunting 3.2.4 on your own Ubuntu server with ox: PHP, MariaDB, a scheduler and the first company and admin made on deploy, from the config that ran. Saurav Sharma Oct 17, 2026 10 min read ## Deploy to a server you own ox sets up systemd, Caddy and PostgreSQL on your Ubuntu server and deploys on every push. Free for your first project. [Sign up with GitHub](https://deploywithox.com/login/github) [Read the docs](https://deploywithox.com/docs)