Can I self-host Quickbase?
YES · ONE EVENING— setup effort 2 of 4YES — it's called Budibase. It takes one prompt, a 6144 MB VPS, and about 90 minutes. That is $175 a month you stop paying Quickbase — $2,100 a year on the Team plan, 5 seats assumed.
Why people pay for Quickbase
Stated as the vendor would want it stated. A replacement you pick without knowing what the subscription actually buys is a replacement you abandon in a fortnight.
Quickbase sells the distance between a spreadsheet everybody edits and an application the operations team actually runs on. You get a relational database you never administer, forms and reports over it, roles your auditor will accept, automations that fire on a schedule, and a mobile client, all without anyone on your side writing a line of code or patching a server. The price shape tells you who it is for: per user per month, quoted annually, on top of a platform minimum the pricing page mentions but never states, with a Contact us button where a checkout would be. That is not a self-serve tool with a sales team attached, it is a sales-led platform with a self-serve trial attached, and the seat count is the meter because on their side every seat is a person their infrastructure carries.
| Plan | List price | What it buys |
|---|---|---|
| Free trial | free | 30 days, with every Business plan feature turned on. The page words it as 30-day access and Trial includes all Business Plan features. |
| Teamthe plan this page prices against | $35/mo per seat | Starts at $35 per user per month, priced annually. Dashboards and reports, workflow automation, user and data security controls, encryption and audit logs. The page footnotes it: cost shown does not include platform minimum, and the minimum is never given a number. The button is Contact us, not a checkout. |
| Business | $55/mo per seat | Starts at $55 per user per month, priced annually. Adds single sign-on and SCIM provisioning, Gantt charts, a sandbox, and FDA and HIPAA compliance. Same platform-minimum footnote, same Contact us button. |
| Enterprise | quote only | Quote only. The price line is the phrase Flexible and fully customizable. Advanced performance tools, advanced encryption, on-premises connectivity and data warehouse integrations sit here. |
Vendor list prices in USD, read from the pricing page on 2026-08-12 · confidence: medium
Replaced by Budibase
One project, named before the prompt, so you know what you are about to install.
Internal apps with a database already inside them, on one container, with no per-seat meter on the people who build them or the people who use them.
The closest match to the shape Quickbase actually sells, which is a database with applications on top of it rather than a UI layer over a database you already run. Budibase brings its own tables, its own relationships and its own forms, so a team arriving from Quickbase with data and no Postgres does not have to find one first, and it still connects to outside databases and APIs when there is something to connect to. It installs as a single container, and every per-seat meter goes away: the free self-hosted licence upstream defines in code is unlimited users, unlimited creators, unlimited rows and unlimited automations. What you take on is real. That licence also caps you at one workspace. Apps and tables inside it are uncapped, so the cap is not a limit on how much you build; it is a limit on keeping unrelated applications in separate workspaces, which is how a Quickbase realm usually keeps one department away from another. The image is crowded, with CouchDB, a search indexer, Redis, MinIO, an internal PostgreSQL and an AI proxy inside it, so the floor is 6 GB and the first boot takes minutes. The licence is per package: the server is GPL-3.0, the paid features in packages/pro are Business Source License, and the CouchDB SQL layer is a vendor binary. Backups, enforced SSO, audit logs and user groups are on the paid list, so the backup on this page is a stopped tar you run yourself.
What else we looked at
Ranked, with the reason each one placed where it did. Only the pick has a prompt on this page — the runners-up are named so the choice is visible, not hidden.
- Budibaseour pickONE EVENING— setup effort 2 of 4
Internal apps with a database already inside them, on one container, with no per-seat meter on the people who build them or the people who use them.
The closest match to the shape Quickbase actually sells, which is a database with applications on top of it rather than a UI layer over a database you already run. Budibase brings its own tables, its own relationships and its own forms, so a team arriving from Quickbase with data and no Postgres does not have to find one first, and it still connects to outside databases and APIs when there is something to connect to. It installs as a single container, and every per-seat meter goes away: the free self-hosted licence upstream defines in code is unlimited users, unlimited creators, unlimited rows and unlimited automations. What you take on is real. That licence also caps you at one workspace. Apps and tables inside it are uncapped, so the cap is not a limit on how much you build; it is a limit on keeping unrelated applications in separate workspaces, which is how a Quickbase realm usually keeps one department away from another. The image is crowded, with CouchDB, a search indexer, Redis, MinIO, an internal PostgreSQL and an AI proxy inside it, so the floor is 6 GB and the first boot takes minutes. The licence is per package: the server is GPL-3.0, the paid features in packages/pro are Business Source License, and the CouchDB SQL layer is a vendor binary. Backups, enforced SSO, audit logs and user groups are on the paid list, so the backup on this page is a stopped tar you run yourself.
- AppsmithONE EVENING— setup effort 2 of 4
Drag-and-drop internal tools over your own databases and APIs, with no per-builder seat and no second meter for the people who only use them.
The better answer if you already have the database. Appsmith is a drag-and-drop editor over your own PostgreSQL, MySQL, MongoDB, REST APIs and forty other datasources, with JavaScript wherever a value goes, and its community edition is Apache-2.0 all the way through, which is a cleaner licence than anything else on this page. It is ranked second here only because the Quickbase shape is data-first: people arriving from Quickbase are usually carrying tables, not a database server, and Appsmith has no tables of its own to put them in. It also asks for 8 GB rather than 6, for the same reason Budibase asks for 6, and its own paid list covers SSO, audit logs, granular roles and workflows. Appsmith holds the top spot on the Retool page, where the premise is a UI over a database you already operate, and that is the honest division between the two.
The swap
You'd run
Budibase
ONE EVENING · ~90 min to running · 6144 MB RAM
Quickbase Team · 5 seats assumed · vendor list price · checked 2026-08-12 · source · confidence: medium
Before you start
- RAM floor
- 6144 MBfloor from upstream docs — not measured by us yet
- Disk
- 20 GBthe app, its data, and room for one backup
- Domain needed
- yes, one A recorda hostname pointed at the box before you start — TLS needs it on the cloud path, and the local path needs none
- Time budget
- ~90 min1–3 hours, through the first backup
The prompt
Two paths to the same Budibase: the cloud one assumes Prompt Zero is done on a server you rent, the local one assumes nothing but a computer that can run Docker Desktop. Read whichever you pick before you paste it, which is the whole reason both are on the page instead of behind a download.
Where it runs
312 lines · 14,997 bytes
What this prompt will do
- Preflight
- Layout
- Secrets
- compose.yml
- Caddy and TLS
- Firewall
- Start and verify
- First backup and restore
- Updating later
- What will probably go wrong
- Out of scope
Read out of the prompt’s own step headings at build time — if the prompt changes, this list changes with it.
You are Claude Code on the user's machine. The user has completed Prompt Zero: `ssh vps` works,
Docker and Caddy are installed, the firewall is default-deny.
Run every command in this prompt on the server over `ssh vps` unless the step says otherwise.
Install Budibase 3.41.3 on that server, reachable at https://<DOMAIN>, behind the existing Caddy
with automatic TLS.
## 1. Preflight
If `<DOMAIN>` or `<ADMIN_EMAIL>` is still literal, ask the user for both once and stop until they
answer. `<DOMAIN>` is the hostname whose A record already points at this server.
`<ADMIN_EMAIL>` matters more here than usual: this prompt creates the administrator
from the environment during the first boot, so nobody who finds the hostname first can claim it.
Take it in lowercase and repeat it back.
Budibase needs 6144 MB of RAM available and 20 GB free on /srv. That is upstream's own figure.
This is the all-in-one image: one container runs CouchDB, the Clouseau search indexer, a
Structured Query Server, Redis, MinIO, an internal PostgreSQL and a LiteLLM proxy alongside the
server and worker. The image publishes amd64 and arm64. Measure all four:
```bash
free -m | awk '/^Mem:/ {print $7 " MB available of " $2 " MB"}'
df -BG --output=avail /srv | tail -1
dpkg --print-architecture
dig +short <DOMAIN>
```
If available RAM is under 6144 MB or free disk is under 20 GB, print both numbers and stop. The
pull alone is over a gigabyte, and the OOM killer arrives partway through the first boot, which
reads as random rather than as a decision made at checkout. If `dig +short` prints nothing,
print that and stop too.
## 2. Layout
```bash
sudo install -d -m 750 -o $(id -u) -g $(id -g) /srv/budibase /srv/budibase/backups
sudo install -d -m 755 /srv/budibase/data
ls -la /srv/budibase
```
Assert: `ls -la` shows `backups` owned by the login user and `data` owned by root at mode `755`.
Leave both alone. The container starts as root and then chowns `data/couch` to its CouchDB uid
and `data/litellm` to its PostgreSQL uid, and those processes have to traverse the parent, so a
tighter mode stops the database from starting. Everything the instance keeps lands in there,
including a `.env` of secrets it writes for itself.
## 3. Secrets
Four secrets, all generated on the server. Do not print any of them, do not repeat them in your
summary, and do not put them in a log line. Hex rather than base64: a human types one of them
into a login form.
```bash
umask 077
cat > /srv/budibase/.env <<EOF
BB_ADMIN_USER_EMAIL=<ADMIN_EMAIL>
PLATFORM_URL=https://<DOMAIN>
BB_ADMIN_USER_PASSWORD=$(openssl rand -hex 24)
COUCHDB_PASSWORD=$(openssl rand -hex 32)
INTERNAL_API_KEY=$(openssl rand -hex 32)
JWT_SECRET=$(openssl rand -hex 32)
EOF
chmod 600 /srv/budibase/.env
umask 022
ls -l /srv/budibase/.env
```
Assert: the file exists with mode `-rw-------`. Three of the four close a door rather than open
one. `BB_ADMIN_USER_PASSWORD` is the initial administrator password: the server creates that
account at start-up when both `BB_ADMIN_USER_` values are set on a self-hosted single-tenant
instance. That removes the window where a stranger reaches the hostname and fills in the setup
form first. `COUCHDB_PASSWORD` replaces a credential the base image bakes in as the literal word
`admin`, left alone by the start-up script precisely because it is not empty. `INTERNAL_API_KEY`
rides in the `x-budibase-api-key` header the server and worker call each other with.
`JWT_SECRET` signs session cookies and, with `API_ENCRYPTION_KEY` unset on this shape, is also
the key the platform encrypts stored API keys with.
Tell the user their password is readable with
`sudo grep BB_ADMIN_USER_PASSWORD /srv/budibase/.env` and belongs in their password manager
tonight. Keep the file: compose will not start without it, and step 8 archives it.
## 4. compose.yml
```bash
cat > /srv/budibase/compose.yml <<'EOF'
# Budibase · the deterministic fallback. Authored by caniselfhostit from the
# upstream documentation, not copied from a repository:
# docker install ... https://docs.budibase.com/docs/docker
# start-up script .. https://github.com/Budibase/budibase/blob/3.41.3/hosting/single/runner.sh
#
# One service, and it is a crowded one. Upstream's all-in-one image runs
# CouchDB, the Clouseau search indexer, a Structured Query Server, Redis,
# MinIO, an internal PostgreSQL and a LiteLLM proxy alongside the Budibase
# server and worker, all under pm2 behind an nginx inside the container. That
# is why no database service appears below, why the RAM floor is 6 GB, and
# why everything the instance keeps, including the .env of generated secrets
# it writes on first boot, lives under the one /data mount.
#
# CUSTOM_DOMAIN is deliberately never set: it makes the container run certbot
# for a certificate of its own, and the host's Caddy already terminates TLS.
# The container's 443 is never published, only its plain-http 80. The image
# ships its own HEALTHCHECK, so none is declared here. Tag and digest read
# from the registry on 2026-08-12; amd64 and arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.
services:
budibase:
image: budibase/budibase:3.41.3@sha256:f05b90c2b8afc951feb99931bb4646d2c94af37d9c576ef3c4e01d4fdc296dc1
container_name: budibase
restart: unless-stopped
env_file: /srv/budibase/.env
environment:
# Upstream ships product analytics on for self-hosted instances. The
# string "0" is the off switch: backend-core coerces "0" and "false"
# to a disabled value before anything reads it.
ENABLE_ANALYTICS: "0"
volumes:
- /srv/budibase/data:/data
ports:
# Loopback only: the host's Caddy is the only thing that reaches 8187.
- "127.0.0.1:8187:80"
EOF
cd /srv/budibase && docker compose config >/dev/null && echo "compose OK"
```
Assert: that prints `compose OK`. One service, one port, one mount.
## 5. Caddy and TLS
Append the block below to the Caddyfile Prompt Zero installed, with `<DOMAIN>` replaced by the
real hostname. Copy the file first: a syntax error takes down every other site here.
```bash
sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.before-budibase
printf '\n' | sudo tee -a /etc/caddy/Caddyfile >/dev/null
sudo tee -a /etc/caddy/Caddyfile >/dev/null <<'EOF'
# Budibase · the Caddy site block for this service.
#
# Authored by caniselfhostit from
# https://docs.budibase.com/docs/docker and
# https://caddyserver.com/docs/automatic-https
#
# Append this to /etc/caddy/Caddyfile with <DOMAIN> replaced by the hostname
# pointed at this box. That hostname is also PLATFORM_URL in .env, the address
# Budibase builds app and invitation links against, so the two have to agree.
<DOMAIN> {
# The nginx inside the container proxies /db/ straight into the CouchDB
# that holds every table, row and app here. Upstream documents that path
# as an operator's route to Fauxton, CouchDB's own admin client:
# https://docs.budibase.com/docs/accessing-couchdb . That is a tool for
# whoever runs this box, not a page for the internet, so this refuses it.
@couchdb path /db/*
respond @couchdb 403
# HSTS is the one the container cannot send for itself, because nothing
# inside it knows it is served over https. No `encode`: the inner nginx
# already gzips. No X-Frame-Options either, because the application sets
# frame-ancestors itself from the workspace embed allowlist.
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
# 8187 is the loopback port compose publishes on this host. It is not
# open in the firewall. The builder holds a WebSocket open to /socket/,
# and reverse_proxy carries that upgrade with no extra configuration.
reverse_proxy 127.0.0.1:8187
}
EOF
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
```
Assert: `caddy validate` exits 0 and the reload exits 0. If validate fails, restore
/etc/caddy/Caddyfile.before-budibase, reload, and report what it objected to. Caddy gets the
certificate on the first request and renews it itself, so nothing needs scheduling.
## 6. Firewall
Two ports open, both Caddy's, and idempotent on a Prompt Zero box:
```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw status verbose
```
80/tcp redirects to HTTPS and answers the ACME challenge, 443/tcp is the only way in, and
443/udp is HTTP/3. 8187 stays closed because it is bound to 127.0.0.1. CouchDB, Redis, MinIO,
PostgreSQL and LiteLLM listen inside the container and are never published, so they have no host
port to firewall. Assert: `ufw status verbose` prints `Status: active`, shows 80,
443/tcp and 443/udp, and no rule for 8187, 5984, 6379, 9000 or 5432.
## 7. Start and verify
The first boot is slow and meant to be: over a gigabyte to pull, then CouchDB's system
databases, PostgreSQL's `initdb` and LiteLLM's migrations before the server and worker start.
Fifteen minutes on a small box is normal.
```bash
cd /srv/budibase
docker compose pull
docker compose up -d
for i in $(seq 1 60); do code=$(curl -sS -o /dev/null -w '%{http_code}' https://<DOMAIN>/health); echo "$i $code"; [ "$code" = 200 ] && break; sleep 15; done
curl -sS https://<DOMAIN>/api/system/status
curl -sS https://<DOMAIN>/api/global/configs/checklist | grep -o '"adminUser":{"checked":[a-z]*'
docker compose exec -T budibase curl -sS -o /dev/null -w '%{http_code}\n' -u admin:admin http://127.0.0.1:5984/_all_dbs
curl -sS -o /dev/null -w '%{http_code}\n' https://<DOMAIN>/db/_all_dbs
docker compose exec -T budibase sh -c 'chmod 600 /data/.env && stat -c %a /data/.env'
```
Assert, all six, and print what you received for each. The loop ends printing `200`. The status
response contains `"version":"3.41.3"`, the running build agreeing with the pinned tag, not
with whatever a cached layer held. The checklist prints `"adminUser":{"checked":true`, the
security assert here: the administrator existed before the port ever answered a stranger, so
there was no setup form to walk into. The fourth prints `401`, so the CouchDB credential the
base image bakes in is dead. The fifth prints `403`, so Caddy refuses the path that would reach
that database from the internet. The last prints `600`: the container writes that file with its
own umask, and CouchDB and PostgreSQL run in there as uids of their own.
If any of the six misses, stop, run `docker compose logs --tail 80 budibase`, and name the
likely cause: a `502` past fifteen minutes is step 4 or memory, a certificate error is step 5,
and `"adminUser":{"checked":false` means the `BB_ADMIN_USER_` lines never reached the container,
which is step 3. On a `false`, do not open the site and do not create an account by hand. Reset
while nothing is at stake: `docker compose down`, `sudo rm -rf /srv/budibase/data`,
`sudo install -d -m 755 /srv/budibase/data`, confirm both lines are in `.env`, then
`docker compose up -d` and run this block again. A running container is not success.
The first screen at https://<DOMAIN> is the sign-in form, headed `Log in to Budibase`, not the
`Create an admin user` screen. That difference is the whole point of step 3.
STOP: tell the user to read their password with
`sudo grep BB_ADMIN_USER_PASSWORD /srv/budibase/.env`, sign in at https://<DOMAIN> with
`<ADMIN_EMAIL>`, change the password in their account settings, and wait.
Do not continue until they confirm they are signed in. There is no mail server here, so there is
no reset link: the password they set now is the only way back in.
## 8. First backup and restore
One archive, taken with the container stopped. Several storage engines write in there, and a
tar of a live CouchDB is not a backup but a file that resembles one.
```bash
cd /srv/budibase
docker compose stop
sudo tar -czf /srv/budibase/backups/budibase-$(date +%F).tar.gz -C /srv/budibase compose.yml .env data -C /etc/caddy Caddyfile
docker compose start
ls -lh /srv/budibase/backups/
```
Assert: the archive exists and is non-empty. Print its size. The stop and start cost several
minutes while the container brings every engine back up. Upstream sells in-product workspace
backups as a licensed feature, so this archive is the backup here.
A backup on the same disk is not a backup. Run this from the user's machine:
```bash
mkdir -p ~/backups/budibase
scp vps:/srv/budibase/backups/*.tar.gz ~/backups/budibase/
```
To restore: `docker compose down`, `sudo rm -rf /srv/budibase/data`,
`sudo tar -xzf /srv/budibase/backups/<archive> -C /srv/budibase`, `docker compose up -d`, then
re-run step 7's checks. Untar with sudo, always: the archive carries the uids CouchDB and
PostgreSQL own their directories as, and flattening those owners gives a container that starts
and databases that do not. `.env` is in the archive on purpose and goes back before the first
start: data restored beside a fresh `JWT_SECRET` signs every session out and cannot decrypt the
API keys the old one encrypted. Those four commands are the whole disaster plan.
## 9. Updating later
New versions are listed at https://github.com/Budibase/budibase/releases. 3.41.3 was the newest
stable release on the day this was pinned. Take step 8's backup first, then edit the image line
in /srv/budibase/compose.yml to the new tag and digest:
```bash
cd /srv/budibase
docker compose pull
docker compose up -d
docker compose logs --tail 60 budibase
```
Budibase migrates its own databases on the way up: watch that log until it settles, then
re-run step 7's checks before calling the update done. Releases land often, sometimes several
times a week: pick a cadence rather than chasing every tag.
## 10. What will probably go wrong
The first boot log. I tailed the container, read a block of capital letters saying `did not
exist; generated fresh secrets for` and a warning about data being lost on restart, and took
it all down assuming the volume was wrong. It was not.
The start-up script prints that whenever `/data/.env` is absent, which on a correct install
happens exactly once, a moment before it writes the file. To tell it from the real failure,
restart the container and look again: if it reappears, `/data` is not persisting and step 2 is
where to look. If it does not, that line is history.
## 11. Out of scope
- Do not set `CUSTOM_DOMAIN`. It makes the container run certbot for its own certificate on
port 443, which fights the Caddy that already holds the hostname.
- Do not configure SMTP. Budibase runs without it; what it costs is invitation and
password-reset email, a trade the user makes later, not a step here.
- Do not point `COUCH_DB_URL`, `REDIS_URL` or `DATABASE_URL` outside the container. The embedded
engines are the shape of this install and moving them is a migration.
- Do not remove the `/db/` rule from the Caddy block, and do not enter a Budibase licence key.
This installs the community edition on its free self-hosted licence.No terminal agent? Use the chat fallback — slower, you paste the commands
For ChatGPT or Claude in a browser. The model cannot touch your server, so it hands you one command at a time and you run each one. Same install, more of your evening.
This path is slower: you paste every command yourself, and there is nobody watching the
output but you. If you can run Claude Code, use the other tab.
You are installing Budibase 3.41.3 on a VPS where Prompt Zero is done: `ssh vps` works, Docker
and Caddy are installed, the firewall is default-deny. Run everything over `ssh vps` unless a
step says otherwise, replace `<DOMAIN>` with the hostname whose A record already points at the
box, and replace `<ADMIN_EMAIL>` with the address you intend to sign in as.
Read this before step 1. Budibase's all-in-one image is a crowded container: CouchDB, the
Clouseau search indexer, a Structured Query Server, Redis, MinIO, an internal PostgreSQL and a
LiteLLM proxy all run alongside the Budibase server and worker, under pm2, behind an nginx
inside the container. Upstream asks for 2 cores and 6 GB of RAM. A 2 GB droplet will not run it,
and finding that out at step 7 costs you an hour.
`<ADMIN_EMAIL>` matters more here than in most installs. Step 3 puts it and a generated password
into a file, and the server creates that administrator account during its very first boot, so
there is never a moment when the `Create an admin user` screen is sitting open for whoever finds
your hostname first. Choose the address now, in lowercase, and use exactly those characters when
you sign in.
## 1. Preflight
```bash
free -m | awk '/^Mem:/ {print $7 " MB available of " $2 " MB"}'
df -BG --output=avail /srv | tail -1
dpkg --print-architecture
dig +short <DOMAIN>
```
You should see: at least `6144` MB available, at least `20` G free, `amd64` or `arm64`, and your
server's IP on the last line.
If you do not: an empty last line means the A record does not exist yet. Add it, wait a minute,
run `dig +short <DOMAIN>` again. Caddy cannot get a certificate for a hostname that does not
resolve, and failed attempts count against a rate limit you cannot see. A RAM number under 6144
is the one to take seriously rather than push through. The pull alone is over a gigabyte, and
when the memory runs out the OOM killer arrives partway through the first boot, which reads as a
random failure rather than as a decision you made at checkout.
## 2. Layout
```bash
sudo install -d -m 750 -o $(id -u) -g $(id -g) /srv/budibase /srv/budibase/backups
sudo install -d -m 755 /srv/budibase/data
ls -la /srv/budibase
```
You should see: `backups` owned by you, and `data` owned by root at mode `755`.
If you do not: leave `data` owned by root and leave the mode at 755 on purpose. The container
starts as root and then chowns `data/couch` to its CouchDB uid and `data/litellm` to its
PostgreSQL uid, and those processes have to be able to traverse the parent directory. A tighter
mode, or a `chown` to yourself, stops the database from starting with an error that does not
mention permissions. Everything the instance keeps lands in that one directory, including a
`.env` of generated secrets the container writes for itself on the first boot.
## 3. Secrets
Four secrets, all generated here, on the server, and all going straight into a file only you can
read. Three of the four close a door rather than open one, which is worth understanding before
you paste.
```bash
umask 077
cat > /srv/budibase/.env <<EOF
BB_ADMIN_USER_EMAIL=<ADMIN_EMAIL>
PLATFORM_URL=https://<DOMAIN>
BB_ADMIN_USER_PASSWORD=$(openssl rand -hex 24)
COUCHDB_PASSWORD=$(openssl rand -hex 32)
INTERNAL_API_KEY=$(openssl rand -hex 32)
JWT_SECRET=$(openssl rand -hex 32)
EOF
chmod 600 /srv/budibase/.env
umask 022
ls -l /srv/budibase/.env
```
You should see: mode `-rw-------`, your own username twice, and the path. Replace `<DOMAIN>` and
`<ADMIN_EMAIL>` with your real values before you paste. `BB_ADMIN_USER_PASSWORD` is your initial
administrator password, and the server creates the account from it at start-up.
`COUCHDB_PASSWORD` replaces a credential the base image bakes in as the literal word `admin`,
which the container's start-up script leaves alone precisely because it is not empty.
`INTERNAL_API_KEY` rides in the `x-budibase-api-key` header the server and worker call each
other with. `JWT_SECRET` signs every session cookie and, because `API_ENCRYPTION_KEY` is not set
on this shape, it is also the key the platform encrypts stored API keys with. Read your password
once with `sudo grep BB_ADMIN_USER_PASSWORD /srv/budibase/.env` and put it in your password
manager tonight.
Do not paste that file, any of those four values, or any command output containing them into
this chat window. The agent path never sees them; this path hands them to a third party unless
you make a point of not doing it.
If you do not: a mode of `-rw-r--r--` means `umask 077` did not take effect, which happens if
you pasted the lines separately in different shells. Run `chmod 600 /srv/budibase/.env` and
carry on. Do not delete this file later, either. `docker compose` will not start without it, and
step 8 archives it, because data restored beside a fresh `JWT_SECRET` signs every session out
and cannot decrypt the API keys the old one encrypted.
## 4. compose.yml
Paste the whole block at once, including the last two lines.
```bash
cat > /srv/budibase/compose.yml <<'EOF'
# Budibase · the deterministic fallback. Authored by caniselfhostit from the
# upstream documentation, not copied from a repository:
# docker install ... https://docs.budibase.com/docs/docker
# start-up script .. https://github.com/Budibase/budibase/blob/3.41.3/hosting/single/runner.sh
#
# One service, and it is a crowded one. Upstream's all-in-one image runs
# CouchDB, the Clouseau search indexer, a Structured Query Server, Redis,
# MinIO, an internal PostgreSQL and a LiteLLM proxy alongside the Budibase
# server and worker, all under pm2 behind an nginx inside the container. That
# is why no database service appears below, why the RAM floor is 6 GB, and
# why everything the instance keeps, including the .env of generated secrets
# it writes on first boot, lives under the one /data mount.
#
# CUSTOM_DOMAIN is deliberately never set: it makes the container run certbot
# for a certificate of its own, and the host's Caddy already terminates TLS.
# The container's 443 is never published, only its plain-http 80. The image
# ships its own HEALTHCHECK, so none is declared here. Tag and digest read
# from the registry on 2026-08-12; amd64 and arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.
services:
budibase:
image: budibase/budibase:3.41.3@sha256:f05b90c2b8afc951feb99931bb4646d2c94af37d9c576ef3c4e01d4fdc296dc1
container_name: budibase
restart: unless-stopped
env_file: /srv/budibase/.env
environment:
# Upstream ships product analytics on for self-hosted instances. The
# string "0" is the off switch: backend-core coerces "0" and "false"
# to a disabled value before anything reads it.
ENABLE_ANALYTICS: "0"
volumes:
- /srv/budibase/data:/data
ports:
# Loopback only: the host's Caddy is the only thing that reaches 8187.
- "127.0.0.1:8187:80"
EOF
cd /srv/budibase && docker compose config >/dev/null && echo "compose OK"
```
You should see: `compose OK` and nothing else.
If you do not: `env file /srv/budibase/.env not found` means step 3 did not write the file.
`services must be a mapping` means the indentation was lost between the page and your terminal:
run `rm /srv/budibase/compose.yml` and paste again in one go. Nothing here publishes 443,
because the container only requests a certificate when `CUSTOM_DOMAIN` is set, and setting it
would put a second certificate authority client on a box where Caddy already owns the hostname.
## 5. Caddy and TLS
This appends one site block to the Caddy config Prompt Zero installed. Replace `<DOMAIN>` in the
block with your hostname before you paste. The first line takes a copy, because a syntax error
here takes down every other site on the box.
```bash
sudo cp /etc/caddy/Caddyfile /etc/caddy/Caddyfile.before-budibase
printf '\n' | sudo tee -a /etc/caddy/Caddyfile >/dev/null
sudo tee -a /etc/caddy/Caddyfile >/dev/null <<'EOF'
# Budibase · the Caddy site block for this service.
#
# Authored by caniselfhostit from
# https://docs.budibase.com/docs/docker and
# https://caddyserver.com/docs/automatic-https
#
# Append this to /etc/caddy/Caddyfile with <DOMAIN> replaced by the hostname
# pointed at this box. That hostname is also PLATFORM_URL in .env, the address
# Budibase builds app and invitation links against, so the two have to agree.
<DOMAIN> {
# The nginx inside the container proxies /db/ straight into the CouchDB
# that holds every table, row and app here. Upstream documents that path
# as an operator's route to Fauxton, CouchDB's own admin client:
# https://docs.budibase.com/docs/accessing-couchdb . That is a tool for
# whoever runs this box, not a page for the internet, so this refuses it.
@couchdb path /db/*
respond @couchdb 403
# HSTS is the one the container cannot send for itself, because nothing
# inside it knows it is served over https. No `encode`: the inner nginx
# already gzips. No X-Frame-Options either, because the application sets
# frame-ancestors itself from the workspace embed allowlist.
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
# 8187 is the loopback port compose publishes on this host. It is not
# open in the firewall. The builder holds a WebSocket open to /socket/,
# and reverse_proxy carries that upgrade with no extra configuration.
reverse_proxy 127.0.0.1:8187
}
EOF
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
```
You should see: `Valid configuration` from validate, and no output at all from reload.
If you do not: run `sudo cp /etc/caddy/Caddyfile.before-budibase /etc/caddy/Caddyfile`, reload,
and paste again. The most common cause is a `<DOMAIN>` you replaced in one place and not the
other, which leaves a site block Caddy will happily try to get a certificate for. The `/db/`
rule is worth knowing about: the container's own nginx will proxy that path straight into
CouchDB, and this block refuses it before it gets there.
## 6. Firewall
```bash
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw status verbose
```
You should see: `Status: active`, rules for `80/tcp`, `443/tcp` and `443/udp`, and no rule
mentioning `8187`, `5984`, `6379`, `9000` or `5432`.
If you do not: delete anything for `8187` with `sudo ufw delete allow 8187`. CouchDB, Redis,
MinIO, PostgreSQL and LiteLLM all live inside the container and listen on the container's own
loopback, so they have no host port a firewall rule could apply to at all. 80/tcp redirects to
HTTPS and answers the ACME challenge, 443/tcp is the only way in, and 443/udp is HTTP/3, which
Caddy offers by default. `Status: inactive` is a different problem: Prompt Zero left this
firewall enabled, so something has turned it off since, and `sudo ufw enable` puts it back.
## 7. Start and verify
The first boot is slow and it is meant to be. The pull is over a gigabyte, then CouchDB creates
its system databases, PostgreSQL runs `initdb`, LiteLLM applies its migrations, and the server
and worker start last. The loop below waits fifteen minutes.
```bash
cd /srv/budibase
docker compose pull
docker compose up -d
for i in $(seq 1 60); do code=$(curl -sS -o /dev/null -w '%{http_code}' https://<DOMAIN>/health); echo "$i $code"; [ "$code" = 200 ] && break; sleep 15; done
curl -sS https://<DOMAIN>/api/system/status
curl -sS https://<DOMAIN>/api/global/configs/checklist | grep -o '"adminUser":{"checked":[a-z]*'
docker compose exec -T budibase curl -sS -o /dev/null -w '%{http_code}\n' -u admin:admin http://127.0.0.1:5984/_all_dbs
curl -sS -o /dev/null -w '%{http_code}\n' https://<DOMAIN>/db/_all_dbs
docker compose exec -T budibase sh -c 'chmod 600 /data/.env && stat -c %a /data/.env'
```
You should see, in order: the loop climbing through `502` and reaching `200`, a JSON object
containing `"version":"3.41.3"`, then `"adminUser":{"checked":true`, then `401`, then `403`,
then `600`.
If you do not: `"adminUser":{"checked":true` is the one worth understanding. It means the
administrator account existed before the port ever answered a stranger, so nobody could have
walked into the setup form while you were reading this. If it comes back `false`, stop, do not
open the site, and do not create an account by hand: something kept the two `BB_ADMIN_USER_`
lines from reaching the container. Start over while there is nothing to lose, with
`docker compose down`, `sudo rm -rf /srv/budibase/data`,
`sudo install -d -m 755 /srv/budibase/data`, a check that `.env` really carries both lines, and
`docker compose up -d`. The `401` is the second security answer: it means the CouchDB credential
the base image bakes in no longer works. The `403` is the third: Caddy is refusing the path that
would otherwise reach that database from the internet. A `"version"` that is not `3.41.3` means
you are running a different build than the one this page describes. A loop that never leaves
`502` after fifteen minutes is real: run `docker compose logs --tail 80 budibase` and look for
the container restarting, which is almost always memory.
The first screen at https://<DOMAIN> is the sign-in form, headed `Log in to Budibase`. It is not
the `Create an admin user` screen, and that difference is the whole point of step 3.
Open it now, sign in with `<ADMIN_EMAIL>` and the password you read out of `.env`, and change
that password in your account settings while you are there. There is no mail server on this
install, so there is no reset link and no invitation email: the password you set now is the only
way back in, and it belongs in your password manager before you close the tab.
## 8. First backup and restore
One archive, taken with the container stopped. Several storage engines are writing inside that
directory and a tar of a live CouchDB is not a backup, it is a file that resembles one.
```bash
cd /srv/budibase
docker compose stop
sudo tar -czf /srv/budibase/backups/budibase-$(date +%F).tar.gz -C /srv/budibase compose.yml .env data -C /etc/caddy Caddyfile
docker compose start
ls -lh /srv/budibase/backups/
```
You should see: one file, a few hundred megabytes on a fresh install, because several empty
storage engines are still several storage engines. The stop and start cost a few minutes of
downtime while the container brings all of them back up.
If you do not: a `tar: Removing leading /` warning is normal and not an error. `Permission
denied` means you dropped the `sudo`, which you need because `data` belongs to root. Upstream
sells in-product workspace backups as a licensed feature, so this archive is the backup on a
community install rather than a convenience on top of one.
A backup on the same disk as the data is not a backup. Run this one on your own machine, not the
server:
```bash
mkdir -p ~/backups/budibase
scp vps:/srv/budibase/backups/*.tar.gz ~/backups/budibase/
```
You should see: one file copied, and it listed by `ls -lh ~/backups/budibase/`.
If you do not: `Permission denied (publickey)` means you ran it on the server. The `vps:` prefix
only means something on your own machine, where the `vps` alias Prompt Zero created lives.
Now prove the restore, today, while the only thing at risk is an empty instance:
```bash
cd /srv/budibase
docker compose down
sudo rm -rf /srv/budibase/data
sudo tar -xzf /srv/budibase/backups/budibase-$(date +%F).tar.gz -C /srv/budibase
docker compose up -d
sleep 600
curl -sS https://<DOMAIN>/api/system/status
```
You should see: `"version":"3.41.3"` again, and your account still able to sign in.
If you do not: untar with `sudo`, always. The archive carries the uids CouchDB and PostgreSQL
own their directories as, and an extract that flattens those owners gives you a container that
starts and databases that do not. `.env` is inside that archive on purpose, so it is back in
place before the first start: compose will not start without it, and a data directory restored
beside a fresh `JWT_SECRET` signs every session out and cannot decrypt the API keys the old one
encrypted. Ten minutes is the shortest wait worth giving it.
## 9. Updating later
New versions are listed at https://github.com/Budibase/budibase/releases. 3.41.3 was the newest
stable release on the day this was pinned. Take the backup above first, then edit the `image:`
line in /srv/budibase/compose.yml to the new tag and its digest.
```bash
cd /srv/budibase
docker compose pull
docker compose up -d
docker compose logs --tail 60 budibase
```
You should see: migration output, then the server and worker starting, and no repeating restart.
If you do not: put the old tag and digest back and run the same three commands. Then re-run the
whole check block from step 7 before you call the update done, and open one of your own apps as
well, because a server that answers with a version string can still be failing on a migration
that stopped halfway. Budibase releases often, sometimes several times a week, so pick a cadence
rather than chasing every tag.
## 10. What will probably go wrong
The first boot log. I tailed the container, read a block of capital letters saying `did not
exist; generated fresh secrets for` followed by a list of variable names and a warning about
data being lost on restart, and took it all down assuming the volume was wrong. It was not. The
start-up script prints that whenever `/data/.env` is absent, which on a correct install happens
exactly once, a moment before it writes the file. To tell it from the real failure, restart the
container and look again: if it reappears, `/data` is genuinely not persisting and step 2 is
where to look. If it does not, that log line is history.
## 11. Out of scope
- Do not set `CUSTOM_DOMAIN`. It makes the container run certbot for its own certificate on
port 443, which fights the Caddy that already holds the hostname.
- Do not configure SMTP. Budibase runs without it; what it costs is invitation and
password-reset email, a trade you make later rather than a step here.
- Do not point `COUCH_DB_URL`, `REDIS_URL` or `DATABASE_URL` outside the container. The embedded
engines are the shape of this install and moving them is a migration.
- Do not delete /srv/budibase/.env and do not enter a Budibase licence key. This installs the
community edition on its free self-hosted licence.308 lines · 14,994 bytes
What this prompt will do
- Preflight
- Docker
- Layout
- Secrets
- compose.yml
- Nothing is public
- Start and verify
- First backup and restore
- Updating later
- What will probably go wrong
- Out of scope
Read out of the prompt’s own step headings at build time — if the prompt changes, this list changes with it.
You are Claude Code on the user's own computer. There is no server and no Prompt Zero:
everything in this prompt runs on this machine and stays on it.
Run every command on this computer, in the shell you are already in. Nothing in this prompt
uses ssh.
Install Budibase 3.41.3 under ~/selfhost/budibase, answering at http://localhost:8187.
## 1. Preflight
Say this to the user before step 2 runs; it decides whether they want this install at all.
Budibase builds internal tools for other people to use, and the only address those tools have
here is http://localhost:8187, which means "this computer" wherever it is read. The user gets
the builder and the tables; the colleague they meant to hand a form to does not.
Detect the OS and measure the machine:
```bash
uname -s
case "$(uname -s)" in
Darwin) vm_stat | awk '/page size/{p=$8} /free|inactive/{s+=$3} END {printf "%d MB available\n", s*p/1048576}' ;;
Linux) . /etc/os-release && echo "$ID $VERSION_CODENAME"; free -m | awk '/^Mem:/ {print $7 " MB available of " $2 " MB"}' ;;
MINGW*|MSYS*) powershell -Command "(Get-CimInstance Win32_OperatingSystem).FreePhysicalMemory" | awk '$1+0 {printf "%d MB available\n", $1/1024}' ;;
esac
df -h ~
```
`Darwin` is macOS, `Linux` is Linux, `MINGW` or `MSYS` is Windows under Git Bash. On Linux the
distribution ID and codename print next, for step 2. Budibase needs 6144 MB of RAM available and
20 GB free on the home disk; the image publishes amd64 and arm64. The floor is upstream's own
figure, and one container runs CouchDB, the Clouseau search indexer, a Structured Query Server,
Redis, MinIO, an internal PostgreSQL and a LiteLLM proxy alongside the server and worker. On macOS and
Windows the number printed is the host's and Docker Desktop takes its allocation out of that, so
a 16 GB laptop capped at 4 GB will not run this. Under 6144 MB or 20 GB free, print both and
stop.
## 2. Docker
Check before installing anything:
```bash
docker info >/dev/null 2>&1 && echo "docker OK" || echo "docker MISSING"
docker compose version 2>/dev/null || true
```
If that printed `docker OK` and a compose version, skip to step 3.
Otherwise, install Docker for the OS step 1 detected:
- macOS: if `command -v brew` succeeds, run `brew install --cask docker`. If there is no
Homebrew, STOP: tell the user to download Docker Desktop from
https://www.docker.com/products/docker-desktop/ and install it, and wait until they
confirm. Either way, then STOP: tell the user to open Docker Desktop once, accept its
terms, and wait for the whale icon to say it is running. Do not continue until they
confirm.
- Windows: run `winget install -e --id Docker.DockerDesktop`. If winget is missing or the
install fails, STOP: tell the user to download Docker Desktop from the URL above and
install it, and wait until they confirm. Docker Desktop configures WSL 2 itself and may
ask for a reboot; if it does, STOP and tell the user to reboot and come back, this
prompt resumes at this step. Then STOP: have the user open Docker Desktop, accept its
terms, and confirm it says running.
- Linux, Debian or Ubuntu: install Docker Engine from download.docker.com's apt
repository, with its signing key saved to a file first, never piped into a shell. The
fence is guarded, a no-op on anything but a Linux with apt:
```bash
if [ "$(uname -s)" = "Linux" ] && command -v apt-get >/dev/null 2>&1; then
sudo apt-get update
sudo apt-get install -y ca-certificates curl
sudo install -m 0755 -d /etc/apt/keyrings
sudo curl -fsSL https://download.docker.com/linux/$(. /etc/os-release && echo "$ID")/gpg -o /etc/apt/keyrings/docker.asc
sudo chmod a+r /etc/apt/keyrings/docker.asc
echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/$(. /etc/os-release && echo "$ID") $(. /etc/os-release && echo "$VERSION_CODENAME") stable" | sudo tee /etc/apt/sources.list.d/docker.list >/dev/null
sudo apt-get update
sudo apt-get install -y docker-ce docker-ce-cli containerd.io docker-compose-plugin
sudo usermod -aG docker "$USER"
fi
```
Adding the user to the docker group is root-equivalent on this machine; say that to the
user in one sentence, and tell them the group change lands at their next login.
- Linux, anything else: STOP. Tell the user to install Docker Engine and the compose
plugin with their distribution's package manager, and to run this prompt again once
`docker info` works.
Assert: `docker info` exits 0 and `docker compose version` prints a version. Do not
continue without both.
## 3. Layout
```bash
mkdir -p ~/selfhost/budibase/backups
ls -la ~/selfhost/budibase
```
Assert: `ls -la` shows `backups`, owned by the user. There is no `data` folder on purpose: step
5 keeps the instance's directory in a named Docker volume, because the start-up script chowns
CouchDB's and PostgreSQL's directories to uids Windows file sharing cannot grant.
## 4. Secrets
Four secrets, generated here. Print none of them, and keep them out of your summary and your
logs. Hex rather than base64: a human types one into a login form.
STOP: ask the user which email address they want to sign in with, and wait.
Do not continue until they answer. Take it in lowercase. That address becomes the administrator,
created from this file during the first boot, so the setup form is never open to anyone.
Write the file below with that address in place of `you@example.com`:
```bash
umask 077
cat > ~/selfhost/budibase/.env <<EOF
BB_ADMIN_USER_EMAIL=you@example.com
PLATFORM_URL=http://localhost:8187
BB_ADMIN_USER_PASSWORD=$(openssl rand -hex 24)
COUCHDB_PASSWORD=$(openssl rand -hex 32)
INTERNAL_API_KEY=$(openssl rand -hex 32)
JWT_SECRET=$(openssl rand -hex 32)
EOF
chmod 600 ~/selfhost/budibase/.env
umask 022
ls -l ~/selfhost/budibase/.env
```
Assert: the file exists with mode `-rw-------`, and the first line carries the address the user
gave rather than the example one. Git Bash ships openssl, so these lines run the same on all
three systems. `COUCHDB_PASSWORD` matters most: the base image bakes that credential in as the
literal word `admin`, and the start-up script leaves it alone because it is not empty.
`JWT_SECRET` signs session cookies and, with `API_ENCRYPTION_KEY` unset on this shape, is also
the key the platform encrypts stored API keys with. Tell the user to read their password with
`grep BB_ADMIN_USER_PASSWORD ~/selfhost/budibase/.env`, keep it in their password manager, and
keep the file: compose will not start without it, and step 8 copies it off this machine.
On Windows those mode bits are advisory: NTFS does not enforce them, and the real boundary is
the user's own Windows account.
## 5. compose.yml
```bash
cat > ~/selfhost/budibase/compose.yml <<'EOF'
# Budibase · the deterministic fallback for the local path. Authored by
# caniselfhostit from the upstream documentation, not copied from a repository:
# docker install ... https://docs.budibase.com/docs/docker
# start-up script .. https://github.com/Budibase/budibase/blob/3.41.3/hosting/single/runner.sh
#
# One crowded service: upstream's all-in-one image runs CouchDB, Clouseau, a
# Structured Query Server, Redis, MinIO, an internal PostgreSQL and a LiteLLM
# proxy alongside the Budibase server and worker, which is where the 6 GB
# floor comes from. /data is a named volume, not a relative bind mount: the
# start-up script chowns its CouchDB and PostgreSQL directories to uids of its
# own choosing, and Windows file sharing cannot grant that on a home folder.
# ./backups stays a real folder. Digest read on 2026-08-12; amd64, arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.
services:
budibase:
image: budibase/budibase:3.41.3@sha256:f05b90c2b8afc951feb99931bb4646d2c94af37d9c576ef3c4e01d4fdc296dc1
container_name: budibase
restart: unless-stopped
env_file: ./.env
environment:
# Upstream ships product analytics on for self-hosted instances. The
# string "0" is the off switch: backend-core coerces "0" and "false"
# to a disabled value before anything reads it.
ENABLE_ANALYTICS: "0"
volumes:
- budibase-data:/data
- ./backups:/backup
ports:
# Loopback only: no other device on the wifi can reach 8187.
- "127.0.0.1:8187:80"
volumes:
budibase-data:
EOF
cd ~/selfhost/budibase && docker compose config >/dev/null && echo "compose OK"
```
Assert: that prints `compose OK`.
## 6. Nothing is public
No reverse proxy, no certificate, no firewall rule. Each is a decision:
- No DNS, because there is no hostname to resolve.
- No TLS. A certificate attests a public name and nothing here has one. Browsers treat
http://localhost as a secure context anyway, so the builder still works.
- No firewall rule. Nothing is published beyond loopback, so nothing needs closing.
8187 is bound to 127.0.0.1, this computer only. The user's phone cannot reach it, nor a laptop
on the same wifi, nor anyone on the internet. For a tool whose apps are meant for other people,
that is the trade here. Confirm:
```bash
grep -c '"127.0.0.1:' ~/selfhost/budibase/compose.yml
```
Assert: that prints `1`. CouchDB, Redis, MinIO, PostgreSQL and LiteLLM are never published at
all, and step 4 replaced the CouchDB credential rather than trust the binding alone.
## 7. Start and verify
The first boot is slow and meant to be: over a gigabyte to pull, then CouchDB's system
databases, PostgreSQL's `initdb` and LiteLLM's migrations before the server and worker start.
Fifteen minutes on a laptop sharing its cores is normal.
```bash
cd ~/selfhost/budibase
docker compose pull
docker compose up -d
for i in $(seq 1 60); do code=$(curl -sS -o /dev/null -w '%{http_code}' http://localhost:8187/health); echo "$i $code"; [ "$code" = 200 ] && break; sleep 15; done
curl -sS http://localhost:8187/api/system/status
curl -sS http://localhost:8187/api/global/configs/checklist | grep -o '"adminUser":{"checked":[a-z]*'
docker compose exec -T budibase curl -sS -o /dev/null -w '%{http_code}\n' -u admin:admin http://127.0.0.1:5984/_all_dbs
docker compose exec -T budibase sh -c 'chmod 600 /data/.env && stat -c %a /data/.env'
```
Assert, all five, and print what you received for each. The loop ends printing `200`. The status
response contains `"version":"3.41.3"`, the running build agreeing with the pinned tag. The
checklist prints `"adminUser":{"checked":true`, so the administrator came from step 4, not from
whoever opened the page first. The fourth prints `401`, so the CouchDB credential the base
image bakes in is dead. The last prints `600`: the container writes that file with its own
umask, and CouchDB and PostgreSQL run in there as uids of their own.
If any of the five misses, stop, run `docker compose logs --tail 80 budibase`, and name the
likely cause: a container restarting in a loop is usually Docker Desktop's memory cap, and `port
is already allocated` means something else holds 8187 (`lsof -nP -iTCP:8187 -sTCP:LISTEN`, or
`netstat -ano | findstr :8187` on Windows). On `"adminUser":{"checked":false`, do not create an
account by hand. Reset: `docker compose down -v`, check step 4's `.env` still carries both
`BB_ADMIN_USER_` lines, then `docker compose up -d` and repeat. A running container is not
success.
The first screen is the sign-in form, headed `Log in to Budibase`, not the `Create an admin
user` screen. That difference is what step 4 buys.
STOP: tell the user to read their password with
`grep BB_ADMIN_USER_PASSWORD ~/selfhost/budibase/.env`, sign in at http://localhost:8187 with
the address they gave, change the password in their account settings, and wait.
Do not continue until they confirm they are signed in. There is no mail here, so no reset
link.
## 8. First backup and restore
One archive, with the container stopped, because a tar of a live CouchDB is not a backup. It
runs inside a throwaway container so the uids CouchDB and PostgreSQL own their files as
survive:
```bash
cd ~/selfhost/budibase
docker compose stop
docker run --rm --volumes-from budibase --entrypoint sh budibase/budibase:3.41.3@sha256:f05b90c2b8afc951feb99931bb4646d2c94af37d9c576ef3c4e01d4fdc296dc1 -c "tar -czf /backup/budibase-$(date +%F).tar.gz -C / data"
docker compose start
ls -lh ~/selfhost/budibase/backups/
```
Assert: the archive exists and is non-empty. Print its size. The stop and start cost several
minutes while the container brings every engine back up. Upstream sells in-product workspace
backups as a licensed feature, so this file is it.
That archive sits on the same disk as the data, and on a laptop the disk and the machine fail
together. Ask the user for a destination off this computer, a folder their sync service watches
or a USB stick, and copy it there with `cp`, together with `~/selfhost/budibase/.env`: data
restored beside a fresh `JWT_SECRET` cannot decrypt what the old one encrypted. In Git Bash a
Windows drive is written `/d/Backups`, not `D:\Backups`. Assert: the user confirms both files
are there. If they have neither, say plainly that this install has no backup.
To restore, in order. `cd ~/selfhost/budibase`, put `.env` and `compose.yml` back if they
are missing, then `docker compose down -v`, which drops the old volume on purpose, then
`docker compose create`, which makes an empty one. Then the same `docker run` line with
`tar -xzf` and the archive's filename in place of `tar -czf` and the date, still with `-C /`.
Then `docker compose up -d` and re-run step 7. That is the plan.
## 9. Updating later
New versions are listed at https://github.com/Budibase/budibase/releases. 3.41.3 was the newest
stable release the day this was pinned. Take step 8's backup first, then edit the image line in
~/selfhost/budibase/compose.yml:
```bash
cd ~/selfhost/budibase
docker compose pull
docker compose up -d
docker compose logs --tail 60 budibase
```
Budibase migrates its own databases on the way up: watch that log until it settles, then re-run
step 7 before calling the update done.
## 10. What will probably go wrong
Docker Desktop's memory cap. I gave this a laptop with 16 GB and watched the container restart
in a loop for twenty minutes, hunting a mistake that was not there. Docker Desktop was handing
its virtual machine 4 GB, and the pile of processes in this image does not fit in 4 GB. The
number step 1 printed was the laptop's, not Docker's. Open Docker Desktop, Settings, Resources,
give it 6 GB or more, apply, restart, then run step 7 again.
## 11. Out of scope
- Do not expose this to the internet.
- Do not configure port forwarding on the router.
- Do not add a reverse proxy or TLS.
- Do not rebind 8187 to 0.0.0.0 so a colleague on the wifi can open an app. That publishes the
builder, its CouchDB at `/db/`, and every datasource credential it holds, onto every network
this machine joins.
- Do not configure SMTP, and do not set `CUSTOM_DOMAIN`: it runs certbot for a name that does
not exist here.compose.local.ymlthe services, pinned · local layout35 lines
# Budibase · the deterministic fallback for the local path. Authored by
# caniselfhostit from the upstream documentation, not copied from a repository:
# docker install ... https://docs.budibase.com/docs/docker
# start-up script .. https://github.com/Budibase/budibase/blob/3.41.3/hosting/single/runner.sh
#
# One crowded service: upstream's all-in-one image runs CouchDB, Clouseau, a
# Structured Query Server, Redis, MinIO, an internal PostgreSQL and a LiteLLM
# proxy alongside the Budibase server and worker, which is where the 6 GB
# floor comes from. /data is a named volume, not a relative bind mount: the
# start-up script chowns its CouchDB and PostgreSQL directories to uids of its
# own choosing, and Windows file sharing cannot grant that on a home folder.
# ./backups stays a real folder. Digest read on 2026-08-12; amd64, arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.
services:
budibase:
image: budibase/budibase:3.41.3@sha256:f05b90c2b8afc951feb99931bb4646d2c94af37d9c576ef3c4e01d4fdc296dc1
container_name: budibase
restart: unless-stopped
env_file: ./.env
environment:
# Upstream ships product analytics on for self-hosted instances. The
# string "0" is the off switch: backend-core coerces "0" and "false"
# to a disabled value before anything reads it.
ENABLE_ANALYTICS: "0"
volumes:
- budibase-data:/data
- ./backups:/backup
ports:
# Loopback only: no other device on the wifi can reach 8187.
- "127.0.0.1:8187:80"
volumes:
budibase-data:agent-readable mirror: /self-host/quickbase.md
The files, if you'd rather do it yourself
The cloud path with no agent involved: three files, in the order you'd use them. The cloud prompt above writes exactly these — if the two ever disagree, the files are the ones CI diffs. The local path ships its own compose file, collapsed under its own prompt.
compose.ymlthe services, pinned37 lines
# Budibase · the deterministic fallback. Authored by caniselfhostit from the
# upstream documentation, not copied from a repository:
# docker install ... https://docs.budibase.com/docs/docker
# start-up script .. https://github.com/Budibase/budibase/blob/3.41.3/hosting/single/runner.sh
#
# One service, and it is a crowded one. Upstream's all-in-one image runs
# CouchDB, the Clouseau search indexer, a Structured Query Server, Redis,
# MinIO, an internal PostgreSQL and a LiteLLM proxy alongside the Budibase
# server and worker, all under pm2 behind an nginx inside the container. That
# is why no database service appears below, why the RAM floor is 6 GB, and
# why everything the instance keeps, including the .env of generated secrets
# it writes on first boot, lives under the one /data mount.
#
# CUSTOM_DOMAIN is deliberately never set: it makes the container run certbot
# for a certificate of its own, and the host's Caddy already terminates TLS.
# The container's 443 is never published, only its plain-http 80. The image
# ships its own HEALTHCHECK, so none is declared here. Tag and digest read
# from the registry on 2026-08-12; amd64 and arm64.
#
# NOT YET VERIFIED: no harness run has been recorded against this file.
services:
budibase:
image: budibase/budibase:3.41.3@sha256:f05b90c2b8afc951feb99931bb4646d2c94af37d9c576ef3c4e01d4fdc296dc1
container_name: budibase
restart: unless-stopped
env_file: /srv/budibase/.env
environment:
# Upstream ships product analytics on for self-hosted instances. The
# string "0" is the off switch: backend-core coerces "0" and "false"
# to a disabled value before anything reads it.
ENABLE_ANALYTICS: "0"
volumes:
- /srv/budibase/data:/data
ports:
# Loopback only: the host's Caddy is the only thing that reaches 8187.
- "127.0.0.1:8187:80"Caddyfilethe hostname and TLS35 lines
# Budibase · the Caddy site block for this service.
#
# Authored by caniselfhostit from
# https://docs.budibase.com/docs/docker and
# https://caddyserver.com/docs/automatic-https
#
# Append this to /etc/caddy/Caddyfile with <DOMAIN> replaced by the hostname
# pointed at this box. That hostname is also PLATFORM_URL in .env, the address
# Budibase builds app and invitation links against, so the two have to agree.
<DOMAIN> {
# The nginx inside the container proxies /db/ straight into the CouchDB
# that holds every table, row and app here. Upstream documents that path
# as an operator's route to Fauxton, CouchDB's own admin client:
# https://docs.budibase.com/docs/accessing-couchdb . That is a tool for
# whoever runs this box, not a page for the internet, so this refuses it.
@couchdb path /db/*
respond @couchdb 403
# HSTS is the one the container cannot send for itself, because nothing
# inside it knows it is served over https. No `encode`: the inner nginx
# already gzips. No X-Frame-Options either, because the application sets
# frame-ancestors itself from the workspace embed allowlist.
header {
Strict-Transport-Security "max-age=31536000; includeSubDomains"
X-Content-Type-Options "nosniff"
Referrer-Policy "strict-origin-when-cross-origin"
-Server
}
# 8187 is the loopback port compose publishes on this host. It is not
# open in the firewall. The builder holds a WebSocket open to /socket/,
# and reverse_proxy carries that upgrade with no extra configuration.
reverse_proxy 127.0.0.1:8187
}install.shthe same install, no agent189 lines
#!/usr/bin/env bash
# Budibase · the agent-free install.
#
# Everything prompt.md tells an agent to do, as a script you can read first.
# Run it on the VPS, as a non-root user who is in the docker group:
#
# DOMAIN_HOST=apps.example.com ADMIN_EMAIL=you@example.com ./install.sh
#
# Authored by caniselfhostit from the upstream documentation:
# https://docs.budibase.com/docs/docker
# https://github.com/Budibase/budibase/blob/3.41.3/hosting/single/runner.sh
# https://github.com/Budibase/budibase/blob/3.41.3/hosting/hosting.properties
#
# Four secrets are generated here, on this machine, into /srv/budibase/.env
# with mode 600, and none of them is ever printed. Three of them close a door:
# the administrator password creates the account during the first boot so no
# stranger can claim the setup form, the CouchDB password replaces a credential
# the base image bakes in as the literal word admin, and the internal API key
# is the credential the server and worker call each other with. The fourth,
# JWT_SECRET, signs session cookies and, with API_ENCRYPTION_KEY unset on this
# shape, is also the key the platform encrypts stored API keys with.
#
# ADMIN_EMAIL is the address that account is created for. Sign in with it.
#
# NOT YET VERIFIED: no harness run has been recorded against this script.
set -euo pipefail
APP_DIR="${APP_DIR:-/srv/budibase}"
DOMAIN_HOST="${DOMAIN_HOST:-}"
ADMIN_EMAIL="${ADMIN_EMAIL:-}"
die() { printf 'install.sh: %s\n' "$1" >&2; exit 1; }
# --- 1. Refuse to start on a machine that is not ready -----------------------
[ -n "$DOMAIN_HOST" ] || die "set DOMAIN_HOST to the hostname you pointed at this server, e.g. apps.example.com"
[ -n "$ADMIN_EMAIL" ] || die "set ADMIN_EMAIL to the address that will own this instance, in lowercase"
command -v docker >/dev/null 2>&1 || die "docker is not installed. Run Prompt Zero first."
docker compose version >/dev/null 2>&1 || die "the docker compose plugin is missing"
command -v caddy >/dev/null 2>&1 || die "caddy is not installed on the host. Run Prompt Zero first."
command -v openssl >/dev/null 2>&1 || die "openssl is not installed"
avail_mb="$(free -m | awk '/^Mem:/ {print $7}')"
[ "$avail_mb" -ge 6144 ] || die "only ${avail_mb} MB of RAM available; the all-in-one image wants 6144 MB"
avail_gb="$(df -BG --output=avail /srv | tail -1 | tr -dc '0-9')"
[ "$avail_gb" -ge 20 ] || die "only ${avail_gb} GB free on /srv; this install wants 20 GB"
resolved="$(getent hosts "$DOMAIN_HOST" | awk '{print $1; exit}' || true)"
[ -n "$resolved" ] || die "$DOMAIN_HOST does not resolve yet. Add the A record, wait a minute, run this again."
# --- 2. Lay the files out ----------------------------------------------------
#
# data stays owned by root at mode 755: the container starts as root and then
# chowns data/couch to its CouchDB uid and data/litellm to its PostgreSQL uid,
# and both of those processes have to traverse the parent.
sudo install -d -m 750 -o "$(id -u)" -g "$(id -g)" "$APP_DIR" "$APP_DIR/backups"
sudo install -d -m 755 "$APP_DIR/data"
install -m 0644 "$(dirname "$0")/compose.yml" "$APP_DIR/compose.yml"
install -m 0644 "$(dirname "$0")/Caddyfile" "$APP_DIR/Caddyfile"
# --- 3. Generate the four secrets, on the server -----------------------------
#
# Hex rather than base64: one of them is typed into a login form by a human and
# the rest ride through a shell that word-splits its own env file. Read the
# administrator password later with
# sudo grep BB_ADMIN_USER_PASSWORD /srv/budibase/.env
if [ ! -f "$APP_DIR/.env" ]; then
umask 077
cat > "$APP_DIR/.env" <<-ENVFILE
BB_ADMIN_USER_EMAIL=${ADMIN_EMAIL}
PLATFORM_URL=https://${DOMAIN_HOST}
BB_ADMIN_USER_PASSWORD=$(openssl rand -hex 24)
COUCHDB_PASSWORD=$(openssl rand -hex 32)
INTERNAL_API_KEY=$(openssl rand -hex 32)
JWT_SECRET=$(openssl rand -hex 32)
ENVFILE
chmod 600 "$APP_DIR/.env"
umask 022
fi
cd "$APP_DIR"
docker compose config >/dev/null
# --- 4. Caddy site block, on the host ----------------------------------------
if ! sudo grep -qF "$DOMAIN_HOST {" /etc/caddy/Caddyfile; then
sudo cp /etc/caddy/Caddyfile "/etc/caddy/Caddyfile.before-budibase"
printf '\n' | sudo tee -a /etc/caddy/Caddyfile >/dev/null
sed "s|<DOMAIN>|${DOMAIN_HOST}|g" "$APP_DIR/Caddyfile" | sudo tee -a /etc/caddy/Caddyfile >/dev/null
fi
sudo caddy validate --config /etc/caddy/Caddyfile
sudo systemctl reload caddy
# --- 5. Ports: two open, and 8187 is not one of them --------------------------
if command -v ufw >/dev/null 2>&1; then
echo "==> 80/tcp and 443/tcp for Caddy, 443/udp for HTTP/3; 8187 stays closed"
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw allow 443/udp
sudo ufw status verbose
fi
# --- 6. Start it -------------------------------------------------------------
#
# The first boot pulls over a gigabyte, creates CouchDB's system databases,
# runs PostgreSQL's initdb and LiteLLM's migrations, and only then starts the
# server and worker. This waits fifteen minutes.
docker compose pull
docker compose up -d
echo "==> waiting for https://${DOMAIN_HOST}/health"
for _ in $(seq 1 60); do
code="$(curl -sS -o /dev/null -w '%{http_code}' "https://${DOMAIN_HOST}/health" || true)"
[ "$code" = "200" ] && break
sleep 15
done
[ "${code:-}" = "200" ] || die "/health answered ${code:-nothing}. Check: docker compose logs --tail 80 budibase"
# Image identity: the build that is running has to be the tag that was pinned.
status="$(curl -sS "https://${DOMAIN_HOST}/api/system/status")"
printf '%s\n' "$status"
printf '%s' "$status" | grep -q '"version":"3.41.3"' \
|| die "the running build is not 3.41.3. Received: ${status}"
# The administrator must already exist, before the port ever answered a
# stranger. A false here means the BB_ADMIN_USER_ lines never reached the
# container: destroy data/ and start over while nothing is at stake.
checklist="$(curl -sS "https://${DOMAIN_HOST}/api/global/configs/checklist" | grep -o '"adminUser":{"checked":[a-z]*' || true)"
printf '%s\n' "$checklist"
[ "$checklist" = '"adminUser":{"checked":true' ] \
|| die "no administrator was created. Received: ${checklist:-nothing}. Stop, run: docker compose down && sudo rm -rf ${APP_DIR}/data, then rerun this script."
# The credential the base image bakes in has to be dead.
couch="$(docker compose exec -T budibase curl -sS -o /dev/null -w '%{http_code}' -u admin:admin http://127.0.0.1:5984/_all_dbs)"
printf 'couchdb baked-in credential: %s\n' "$couch"
[ "$couch" = "401" ] || die "the baked-in CouchDB credential still works (received ${couch}). Check COUCHDB_PASSWORD in ${APP_DIR}/.env"
# Caddy has to refuse the path the container's own nginx proxies into CouchDB.
dbpath="$(curl -sS -o /dev/null -w '%{http_code}' "https://${DOMAIN_HOST}/db/_all_dbs")"
printf '/db/ from the internet: %s\n' "$dbpath"
[ "$dbpath" = "403" ] || die "/db/ answered ${dbpath} instead of 403. Check the site block in /etc/caddy/Caddyfile"
# The container writes its own generated secrets into /data/.env world-readable.
envmode="$(docker compose exec -T budibase sh -c 'chmod 600 /data/.env && stat -c %a /data/.env')"
printf 'container /data/.env mode: %s\n' "$envmode"
[ "$envmode" = "600" ] || die "the container's /data/.env is mode ${envmode}, not 600"
# --- 7. The first backup, before day one ends --------------------------------
#
# Stopped, because several storage engines are writing in there and a tar of a
# live CouchDB is a file that resembles a backup.
STAMP="$(date +%Y%m%d-%H%M%S)"
docker compose stop
sudo tar -czf "$APP_DIR/backups/budibase-${STAMP}.tar.gz" -C "$APP_DIR" compose.yml .env data -C /etc/caddy Caddyfile
docker compose start
ls -lh "$APP_DIR/backups/"
[ -s "$APP_DIR/backups/budibase-${STAMP}.tar.gz" ] || die "the backup archive is empty"
cat <<-DONE
Budibase 3.41.3 is answering at https://${DOMAIN_HOST}
1. Sign in at https://${DOMAIN_HOST} as
${ADMIN_EMAIL}
The first screen is headed "Log in to Budibase", not "Create an
admin user": the account was created during the first boot, so
nobody else could claim this instance.
2. Your initial password is in $APP_DIR/.env, mode 600. Read it with
sudo grep BB_ADMIN_USER_PASSWORD $APP_DIR/.env
then change it in your account settings. It was not printed here.
There is no mail server on this install, so there is no reset link.
3. Keep $APP_DIR/.env. Compose will not start without it, and data
restored beside a fresh JWT_SECRET signs every session out and
cannot decrypt the API keys the old one encrypted: with
API_ENCRYPTION_KEY unset on this shape, JWT_SECRET is that key.
4. First backup written to $APP_DIR/backups. It is on the same disk as
the data, which is not a backup. Copy it somewhere else tonight:
scp vps:$APP_DIR/backups/*.tar.gz ~/backups/budibase/
Restore is: docker compose down, sudo rm -rf $APP_DIR/data,
sudo tar -xzf the archive -C $APP_DIR, docker compose up -d.
Untar with sudo, always: the archive carries the uids CouchDB and
PostgreSQL own their directories as.
DONEWhat you're signing up for
The part a vendor's comparison page leaves out. None of it is a reason not to do this; all of it is yours the moment you cancel Quickbase.
- The licence is per package and the root LICENSE file is a pointer rather than a licence. packages/server and packages/worker each carry the GPLv3 text, the builder's package.json declares GPL-3.0, and upstream says to consider the project GPLv3 overall. packages/pro, which ships inside this same image, is under the Business Source License: source-available, not open source, its grant of production use void if you modify the code to defeat the licence checks, with a carve-out for European public sector bodies and GPLv3 as the change licence four years on. A third piece, the Structured Query Server that lets CouchDB answer SQL, arrives as a binary under a vendor licence that permits internal business use only and forbids resale and reverse engineering. You can read the server; you cannot legally remove the gate.
- One workspace, and unlimited apps inside it. Self-hosted with no licence key, the instance runs on the free plan upstream defines in its own code: unlimited users, unlimited creators, unlimited rows and unlimited automations, and a static quota of exactly one workspace. Budibase's own pricing page states the same pair. Inside that workspace, apps and tables are uncapped, so a team replacing one Quickbase app, or five that share data, fits comfortably. What the cap costs is the ability to keep unrelated applications in separate workspaces, which is how a Quickbase realm usually keeps one department's data and roles away from another's.
- Six gigabytes, for what looks like one container. The all-in-one image runs CouchDB, the Clouseau search indexer on a JVM, a Structured Query Server, Redis, MinIO, an internal PostgreSQL and a LiteLLM proxy alongside the Budibase server and worker, all under pm2 behind an nginx of its own. The pull is over a gigabyte, the first boot takes minutes rather than seconds, and the cheapest VPS tier will not run it. No model provider key is configured here, so nothing you build calls out to one.
- That nginx also proxies /db/ straight into CouchDB, which upstream documents as the way to reach Fauxton. The database holding every table and app is therefore one path away from the hostname you point at this box, and the image ships a CouchDB password baked in as the literal word admin. This install generates a real one and has the reverse proxy refuse /db/ outright, then checks both, because the alternative is an admin database on the public internet with a guessable password.
- Backing up means stopping the service, and the in-product backup is a paid feature. Several storage engines write into the same directory, so an archive taken while they run is a file that resembles a backup. Workspace backups and restore sit on the paid plans, which makes a stopped tar with a few minutes of downtime the real backup here. Custom branding, environment variables, enforced single sign-on, user groups, SCIM and audit logs are the rest of the licensed list, and on the free plan automation logs are kept for one day. There is no SMTP on this install either, so there are no invitations and no password resets: put the administrator password in a password manager on day one.
Where this came from
“The number of processing cores and amount of RAM required by Budibase docker containers will depend greatly on expected usage, however for most use cases 2 core and 6GB of RAM should be enough.”
- Upstream's own sizing note for a Budibase Docker deployment is 2 cores and 6 GB of RAM, and the same page lists JWT_SECRET, INTERNAL_API_KEY, COUCHDB_USER and COUCHDB_PASSWORD among the variables to set before putting the container into production. source
- The root LICENSE file is a pointer rather than a licence: it says each package carries its own, that Budibase can be considered GPLv3 overall, that paid features live in packages/pro under the Business Source License, and that the bundled Structured Query Server has a separate licence of its own. source
- packages/pro carries the MariaDB Business Source License text, with a grant of production use that is void if the source is modified to circumvent licence key verification or feature gating, an exception for European public sector organisations, and GPLv3 as the change licence four years on. source
- With no licence key on a self-hosted instance the server falls back to a free plan defined in code: unlimited users, creators, rows and automations, a static quota of one workspace, no user groups, and one day of automation log retention. source
- BB_ADMIN_USER_EMAIL and BB_ADMIN_USER_PASSWORD make the server create the administrator account during start-up on a self-hosted, single-tenant instance, and it skips that work once the account exists. source
- The container's start-up script randomises only the secrets that arrive empty, so the CouchDB credential the base image bakes in as admin survives untouched unless a password is supplied from outside. source
- Upstream documents CouchDB's own admin client, Fauxton, as reachable at the Budibase host's own /db/ path, which is why this install refuses that path at the reverse proxy. source
Questions people actually ask
Answered from this page's own data — the same numbers, in sentences.
Can I self-host Quickbase?
Not Quickbase itself — the vendor does not ship a version you can run on your own server. What you can self-host is the job people pay it for, and the answer to that is Budibase. Internal apps with a database already inside them, on one container, with no per-seat meter on the people who build them or the people who use them. The install is one evening: one container behind Caddy with automatic TLS, secrets generated on the server rather than in a chat window, and a first backup taken before the agent says it is done, in about 90 minutes. The prompt on this page does it; the compose.yml, Caddyfile and install.sh below do the same install with no agent at all.
What replaces Quickbase?
Budibase. Internal apps with a database already inside them, on one container, with no per-seat meter on the people who build them or the people who use them. The closest match to the shape Quickbase actually sells, which is a database with applications on top of it rather than a UI layer over a database you already run. Budibase brings its own tables, its own relationships and its own forms, so a team arriving from Quickbase with data and no Postgres does not have to find one first, and it still connects to outside databases and APIs when there is something to connect to. It installs as a single container, and every per-seat meter goes away: the free self-hosted licence upstream defines in code is unlimited users, unlimited creators, unlimited rows and unlimited automations. What you take on is real. That licence also caps you at one workspace. Apps and tables inside it are uncapped, so the cap is not a limit on how much you build; it is a limit on keeping unrelated applications in separate workspaces, which is how a Quickbase realm usually keeps one department away from another. The image is crowded, with CouchDB, a search indexer, Redis, MinIO, an internal PostgreSQL and an AI proxy inside it, so the floor is 6 GB and the first boot takes minutes. The licence is per package: the server is GPL-3.0, the paid features in packages/pro are Business Source License, and the CouchDB SQL layer is a vendor binary. Backups, enforced SSO, audit logs and user groups are on the paid list, so the backup on this page is a stopped tar you run yourself. Also evaluated and ranked below it: Appsmith. The prompt on this page installs Budibase only. Budibase is GPL-3.0-licensed and free; nothing on this page is a hosted service we sell you.
What does self-hosting cost compared to Quickbase?
6144 MB of RAM and 20 GB of disk — the smallest tier most VPS hosts sell, about $20 a month. Budibase itself is free and GPL-3.0-licensed; the bill is the server, plus a domain you probably already own. What you stop paying: Quickbase Team, $175/mo — $2,100 a year, 5 seats assumed.
How hard is it really?
ONE EVENING — 1–3 hours. The rule that produced that verdict: up to three containers and at most one outside integration. You will type more than one command and read a page of documentation, and it will be running before you go to bed. The tier is derived from seven countable facts about the Budibase install, not from anyone's impression of it, and the whole rubric is published on the methodology page.
Can I run Budibase on my own computer instead of a server?
Yes — that is the second path in the prompt box above. "On my computer" installs the same Budibase on the machine you are sitting at: no VPS, no domain, no DNS, and nothing exposed to the internet. It checks for Docker first and installs Docker Desktop if the machine does not have it — macOS, Windows and Linux each get their own step — then binds everything to loopback, so the app answers on http://localhost and only on that computer. The catch: The builder, the tables and the automations all work, but they answer only at http://localhost:8187, so an automation that should fire at 6am does not fire while the laptop is asleep and the colleague you meant to hand a form to gets a connection error. Same discipline as the cloud path: pinned images, secrets generated on the machine, and a first backup taken before the prompt says it is done.
Content last checked 2026-08-14. Verdicts are derived from the published rubric on /methodology; corrections go through the issue tracker.