How to Deploy Baserow on a VPS
Updated Sep 2026
verified on Ubuntu 26.04 · Sep 2026Self-host Baserow, the open-core Airtable alternative, on your own VPS. One all-in-one container behind Caddy, with the backup command that captures everything.
- A VPS with at least 2 GB RAM (the all-in-one container idles around 1.2 GB)
- A fresh Ubuntu 24.04 or 26.04 server with root/sudo SSH access
- A domain you can point at the server
- Docker Engine + Compose installed (see the base guide below)
What Baserow is
Baserow is an open-core no-code database built on Django and PostgreSQL. Your data sits in tables that look like spreadsheets. Every table gets a REST API that documents itself. The same install also includes an application builder, dashboards and automations. It is licensed MIT, with premium features sold on top.
Most readers are choosing between it and NocoDB. NocoDB vs Baserow goes through the differences. In deployment terms, the main one is that Baserow ships as one all-in-one image. The Baserow backend, the web frontend, PostgreSQL, Redis, the Celery workers and an internal Caddy all run in a single container with a single data volume. That makes it one of the easiest stateful apps on this site to run and back up.
Server sizing
On our test box (GCP e2-standard-2, Ubuntu 26.04, Docker), the all-in-one container idled at about 1,167 MB of RAM and used about 2.3 GB of disk. The catalog's minimum is 2 GB RAM:
- 2 GB RAM / 1–2 vCPU: one person or a small team. Add swap as a safety margin.
- 4 GB RAM / 2 vCPU: the comfortable choice. Imports, exports and snapshots run on background workers inside the container and need headroom.
- Disk: 20–40 GB. Uploaded files are stored in the data volume unless you configure S3-compatible storage.
If memory is tight, upstream documents a smaller mode. Setting
BASEROW_RUN_MINIMAL=yes together with BASEROW_AMOUNT_OF_WORKERS=1 starts
fewer internal processes. The cost is that slow background jobs can then hold
up real-time updates.
Prepare the server
This guide assumes Docker Engine and the Compose plugin are installed. If not, work through Docker & Compose on Ubuntu first. Open SSH and the web ports:
sudo ufw allow OpenSSH
sudo ufw allow 80
sudo ufw allow 443
sudo ufw --force enable
sudo ufw status verbose
Baserow's own docs point out a trap here. Ports that Docker publishes on
0.0.0.0 bypass ufw, so the firewall rules above would not protect them.
The install below publishes Baserow on 127.0.0.1 only, which avoids the
problem.
Paid link — we earn a commission if you shop through it.
Install Baserow (Docker Compose)
Upstream documents three ways to run the image: a quick-start docker run,
automatic HTTPS through the embedded Caddy, and "behind a reverse proxy already
handling SSL". This guide uses the third, written as a Compose file. Your
host's Caddy handles TLS, and Baserow listens on a loopback port.
mkdir -p ~/baserow && cd ~/baserow
cat > docker-compose.yml <<'YAML'
services:
baserow:
image: baserow/baserow:2.3.4
container_name: baserow
restart: unless-stopped
environment:
# Must match the address you type in the browser, scheme included.
BASEROW_PUBLIC_URL: https://baserow.example.com
volumes:
- baserow_data:/baserow/data
ports:
# Loopback only: Caddy on the host is the public entry point.
- "127.0.0.1:8080:80"
volumes:
baserow_data:
YAML
Replace baserow.example.com with your hostname. BASEROW_PUBLIC_URL is the
setting that matters most. Baserow treats any other address as a published
application, so a mismatch shows up as confusing 404s and broken links, not as
a clear error.
Start it:
cd ~/baserow
docker compose up -d
The first start migrates the internal database and can take a few minutes. Wait until the container reports healthy:
cd ~/baserow
for i in $(seq 1 60); do
status=$(docker inspect -f '{{.State.Health.Status}}' baserow 2>/dev/null)
echo "health: $status"; [ "$status" = "healthy" ] && break; sleep 10
done
docker compose ps
Watch progress with docker compose logs -f baserow if you like. It runs
forever, so press Ctrl+C to leave it.
The container generates its own internal secrets (the Django secret key and the
database password) on first start and keeps them in /baserow/data. That
means the data volume is the whole installation, secrets included. Back it up
as a unit.
HTTPS + domain
Point an A record for baserow.example.com at the server, then proxy to the
loopback port with Caddy. The steps are in
Automatic HTTPS with Caddy:
baserow.example.com {
reverse_proxy 127.0.0.1:8080
}
Caddy passes WebSocket upgrades through without extra configuration. Baserow
needs them for real-time collaboration. If you use a different proxy, make sure
it forwards the Upgrade and Connection headers.
You could instead let Baserow's embedded Caddy get certificates by itself
(BASEROW_CADDY_ADDRESSES=:443 and publishing ports 80 and 443). That is the
option on the Baserow page. It is fine on a box that runs
only Baserow. A host-level Caddy is the better choice once you run more than
one app.
First login and securing it
Open https://baserow.example.com and create your account straight away. On
a fresh instance, the first account to sign up gets staff (admin) rights.
Baserow sets that when there are no other users. After that:
- Decide who may sign up. The admin settings page controls whether new accounts can be created. Turn sign-ups off if the instance is private, and invite people into workspaces instead.
- Keep port 8080 on loopback. Publishing it on
0.0.0.0would bypassufw, as described above. - Set up SMTP if you want invitations and password resets to arrive. The email variables are listed in Baserow's configuration reference.
- Consider an external PostgreSQL for production. Upstream recommends it.
The all-in-one image accepts
DATABASE_URL=postgresql://user:pwd@host:port/db, andREDIS_URLfor Redis.
Backups
Everything, including PostgreSQL, uploaded files and the generated secrets, is
in the baserow_data volume. Upstream's full backup is a tar of that volume
with Baserow stopped. Compose prefixes the volume with the project
directory, so check the real name with docker volume ls:
cd ~/baserow
mkdir -p backups
docker compose stop
docker run --rm -v baserow_baserow_data:/baserow/data:ro -v "$PWD/backups":/backup ubuntu \
tar czf /backup/baserow-data-$(date +%F).tar.gz /baserow/data
docker compose start
ls -lh backups/
Upstream also documents a database-only backup through the image's own CLI. It
also needs the instance stopped. Write the file into the volume's own
backups folder: the CLI runs as Baserow's internal user, which cannot write
to a host directory owned by you. Then copy it out with docker cp, which
works on a stopped container too:
cd ~/baserow
docker compose stop
docker run --rm -v baserow_baserow_data:/baserow/data baserow/baserow:2.3.4 \
backend-cmd-with-db backup -f /baserow/data/backups/baserow-db-$(date +%F).tar.gz
docker cp baserow:/baserow/data/backups/baserow-db-$(date +%F).tar.gz backups/
docker compose start
ls -lh backups/
To restore, run the matching backend-cmd-with-db restore -f … against a
new, empty data volume. For the full tar, unpack it into a fresh volume and
point the Compose file at that volume. Copy backups/ off the server. A backup
that only exists on the machine it protects doesn't help when that machine
fails.
Upgrades
Baserow upgrades itself on startup. Upstream's procedure is: back up, stop, change the image tag, start, and watch the logs.
cd ~/baserow
docker compose pull
docker compose up -d
To move to a new release, change baserow/baserow:2.3.4 in
docker-compose.yml to the new tag first, then run the two commands above.
Take the full volume backup before every version change. A pinned tag means
nothing changes until you decide it should.
Troubleshooting
Links and pages 404, or the app says the domain isn't found.
BASEROW_PUBLIC_URL doesn't match the address in the browser. Check the scheme,
the host and any port. Fix it and run docker compose up -d.
The first start seems stuck, or shows unhealthy. Initial migrations take
a while on a small box. In our test the container needed about four minutes
and reported unhealthy for a short time before it turned healthy. Follow
docker compose logs -f baserow and give it time before you intervene.
Real-time updates don't appear until you refresh. WebSocket upgrades are being removed between the browser and Baserow. Caddy passes them by default, so look at any CDN or custom proxy in the path.
The box runs out of memory during an import. The container idles around
1.2 GB and background jobs push it higher. Move to 4 GB, add swap, or try
BASEROW_RUN_MINIMAL=yes with BASEROW_AMOUNT_OF_WORKERS=1.
Verification + next steps
You're done when:
https://baserow.example.comloads over a valid certificate;- your account is the staff admin and sign-ups are set the way you want;
- a test table survives
docker compose restart; - a dated volume backup exists somewhere other than this server.
From there, try the auto-generated REST API with a database token, or set up the application builder on top of your tables. For a comparison, see NocoDB on a VPS. For ranked hosts, see Best VPS for databases.