Baserow HA - highly available no-code database
Why this product
Baserow does not cluster itself. A single VM with an embedded database is a single point of failure, and building a resilient installation by hand means wiring external PostgreSQL and Redis, object storage, a load balancer, TLS and a single scheduler yourself, then keeping them consistent. This product does that work and delivers a deployment that survives the loss of an availability zone.
- Nothing durable on the nodes. The application database lives in a Managed Service for PostgreSQL cluster, the cache and task broker in a Managed Service for Redis (Valkey) cluster, and uploaded files and exports in an Object Storage bucket. Application nodes are disposable by design.
- Three zones, real quorum. Both managed clusters are created with three hosts, one per availability zone, and the application and worker nodes are split across two zones behind a network load balancer. The loss of one zone leaves each cluster with a quorum and the service reachable.
- Endpoints that follow the master. The nodes address both clusters through the cluster read-write FQDN, so a master promotion is transparent to the application. This is verified by an acceptance test that promotes a replica in both clusters and then writes.
- Exactly one scheduler, always. Celery beat must run once cluster-wide or periodic jobs duplicate. A leadership daemon holds a PostgreSQL advisory lock and runs beat only on the node holding it; if that node fails, a peer takes over within seconds and never runs a second beat.
- Secrets in Lockbox, never in a form field. Database, Redis and Django signing-key credentials are Lockbox secrets you select or create at deploy time. VM metadata carries secret identifiers only; the bootstrap reads the values at first boot with short-lived IAM tokens from the metadata service, with no service-account key file anywhere.
- First boot without the internet. The image bakes the Baserow and Caddy container images, so a node configures and starts the stack without pulling anything at boot.
Full description
Baserow HA deploys Baserow on Ubuntu 24.04 as two application nodes and two worker nodes with all durable state in managed Yandex Cloud services. Each application node runs the Baserow backend on gunicorn, the Nuxt web frontend, and a Caddy reverse proxy that terminates TLS and routes API, WebSocket and assistant traffic to the backend and everything else to the frontend. Each worker node runs the Celery workers and, on exactly one of them, the Celery beat scheduler. A network load balancer publishes the application nodes on port 443 with a TCP health check; the worker nodes are not publicly reachable.
The state layer is a three-host Managed Service for PostgreSQL cluster and a three-host Managed Service for Redis (Valkey) cluster, one host per availability zone in each, plus an Object Storage bucket for uploaded files and exports. The nodes reach both clusters through the cluster read-write name, so when the service promotes a different host the application follows it without reconfiguration.
Celery beat, the periodic-task scheduler, must run exactly once across the installation or scheduled jobs run several times. A leadership daemon on each worker node holds a PostgreSQL session-level advisory lock and runs the beat container only while it holds that lock. When the holding node stops, a peer acquires the lock and starts beat; when the old holder returns, it stays passive. There is always exactly one scheduler.
At first boot a bootstrap binary reads the deployment parameters from VM metadata, fetches the credentials from the Lockbox secrets you selected, renders the Baserow environment and the Caddy configuration, waits until PostgreSQL and Redis are reachable, and starts the stack from the pre-baked images. It then disables itself, so a reboot never re-runs first-boot configuration.
The form asks only for what an operator must decide: a name prefix, three subnets, an SSH key and the networks allowed to use it, the public URL, the storage class for uploads, the deployment size, and the Lockbox secrets holding the credentials. Sizing, versions, worker counts, ports and disk sizes are fixed at tested values.
Key features
- Two application nodes and two worker nodes across two availability zones, behind a network load balancer on port 443.
- Three-host Managed Service for PostgreSQL 16 and three-host Managed Service for Redis (Valkey) 7.2, one host per zone.
- Object Storage for uploaded files and exports, with a choice of the standard or the smart (automatically tiered) storage class.
- A single Celery beat elected through a PostgreSQL advisory lock, with automatic takeover.
- TLS terminated by Caddy on each node, with your own certificate from Lockbox or a self-signed one.
- All credentials as Lockbox secrets; metadata carries identifiers only.
- SSH access to every node for the
ubuntuuser, restricted to the networks you specify. - A service account limited to two roles: object-storage editor and Lockbox payload viewer.
Before you deploy
Create three Lockbox secrets in the folder you deploy into. Each holds a single entry, and the deployment reads the value from it:
| Secret | Entry key | Value |
|---|---|---|
| PostgreSQL password | password |
Generate in the console |
| Redis password | password |
Generate in the console |
| Django signing key | secret-key |
Generate in the console, at least 50 characters |
Optionally create a fourth secret with your TLS certificate in entries tls-cert (full chain, PEM) and tls-key (private key, PEM). Leaving the TLS field empty makes each node serve a self-signed certificate, which browsers will warn about.
You also need three subnets in one VPC network, one per availability zone, and a public DNS name you can point at the load balancer address after deployment.
Creating the secrets
With the Yandex Cloud CLI, in the folder you deploy into:
yc lockbox secret create --name baserow-db-password \
--payload '[{"key":"password","text_value":"'"$(openssl rand -hex 24)"'"}]'
yc lockbox secret create --name baserow-redis-password \
--payload '[{"key":"password","text_value":"'"$(openssl rand -hex 24)"'"}]'
yc lockbox secret create --name baserow-secret-key \
--payload '[{"key":"secret-key","text_value":"'"$(openssl rand -base64 48)"'"}]'
The database and Redis passwords are generated as hexadecimal on purpose: those values go into a connection string, where characters such as @, : and / would break parsing. The Django signing key is only read as an environment variable, so any long random value is fine.
Optional, for outbound mail and for your own certificate:
yc lockbox secret create --name baserow-smtp-password \
--payload '[{"key":"password","text_value":"<relay password>"}]'
yc lockbox secret create --name baserow-tls \
--payload '[{"key":"tls-cert","text_value":"<full PEM chain>"},{"key":"tls-key","text_value":"<PEM private key>"}]'
With Yandex Postbox the relay password is the SECRET of an API key that has the yc.postbox.send scope, and the SMTP user is that key’s identifier (aje...). A static access key (YCAJE...) is a different kind of credential and the relay rejects it with 535 Authentication credentials invalid.
In the console: Lockbox -> Create secret -> add one key/value entry, using exactly the entry key from the table above. A different key name makes the deployment stop at first boot rather than start with a credential it cannot read. Then select each secret in the matching field of the deployment form.
Deployment parameters
| Parameter | Widget | Notes |
|---|---|---|
| Resource name prefix | text | 3 to 24 characters, lowercase letters, digits and hyphens, starting with a letter |
| VPC subnet in zone A / B / D | subnet picker | One per zone, all in the same network. Zone D carries only the third host of each cluster |
| SSH public key for VM access | text | OpenSSH format; grants access to the ubuntu user on all four nodes |
| CIDR blocks allowed to use SSH | list | Restrict to your own address for production; the default allows any |
| Baserow public URL | text | https://your.domain; sets the TLS host and the links the application generates |
| Storage class for uploaded files | dropdown | Standard, or Smart for automatic tiering when files are read rarely |
| SMTP relay host | text | Optional. Without it no mail is delivered: password resets and invitations reach nobody |
| SMTP port | text | 587 for STARTTLS, 465 for implicit TLS |
| SMTP user | text | Optional; leave empty for a relay that accepts unauthenticated submission |
| SMTP from address | text | For example baserow@example.com |
| Lockbox secret: SMTP password | Lockbox picker | Entry password; required when an SMTP user is set |
| Deployment size | dropdown | Production or Development; changes host classes and disks only, never the topology |
| Lockbox secret: PostgreSQL password | Lockbox picker | Entry password |
| Lockbox secret: Redis password | Lockbox picker | Entry password |
| Lockbox secret: Django SECRET_KEY | Lockbox picker | Entry secret-key |
| Lockbox secret: TLS certificate | Lockbox picker | Optional; entries tls-cert and tls-key |
VM specifications
| Deployment size | Application and worker nodes | Managed PostgreSQL | Managed Redis (Valkey) |
|---|---|---|---|
| Production | 4 vCPU (100 percent), 8 GB RAM, 30 GB network SSD | s3-c2-m8, 20 GB network SSD, three hosts |
hm3-c2-m8, 16 GB network SSD, three hosts |
| Development | 2 vCPU (50 percent), 4 GB RAM, 30 GB network HDD | c3-c2-m4, 20 GB network HDD, three hosts |
b3-c1-m4, 16 GB network SSD, three hosts |
Both sizes create the same four VMs and the same three-host clusters. Development is cheaper per host and uses the prestable update channel; it is intended for evaluation, not for production data.
After deployment
Point your DNS name at the load balancer address shown in the application overview, then open https://your.domain and create the first account. The account created first becomes the instance administrator.
The backend runs the initial database migration under a lock on first boot, so the site answers with an error for roughly three minutes after the nodes start. Wait until https://your.domain/api/_health/ returns OK before using the installation.
For maintenance, connect to any node as ubuntu with the key you supplied. The stack lives in /opt/baserow, is managed by Docker Compose, and its logs are available through journalctl -u baserow-bootstrap and docker compose logs.
Ports
| Port | Direction | Source | Purpose |
|---|---|---|---|
| 443 | inbound | any | HTTPS to the application, terminated by Caddy on each node |
| 443 | inbound | load balancer health checks | TCP health check of the application nodes |
| 22 | inbound | the CIDR blocks you specify | SSH management access |
| all | internal | the three subnets | Application nodes to the managed clusters |
-
Replacing shared spreadsheets
- One source of data instead of files mailed around in copies
- Per-workspace access rights and a change history on every row
- Grid, kanban, calendar, gallery and form views over the same table
-
An internal register or a lightweight CRM
- Built from the interface, without development
- Links between tables, formulas, filters and sorting
- Attachments stored in Object Storage rather than on a node
-
A backend for your own application
- The same REST API the interface uses, with a per-table database token
- Row create, read, update and delete under
Authorization: Token <...> - Interactive reference served by the installation at
/api/redoc/
-
Collecting requests through a form
- A public form view writes straight into the table
- Webhooks notify your systems of
rows.createdand other events - Email notifications and invitations when SMTP is configured
-
Regular data hand-off to other systems
- CSV export on demand or from a script through the API
- Export runs on the dedicated worker nodes, not on those serving the interface
- Result stored in Object Storage and fetched by URL
OpenNix provides technical support to users in Yandex Cloud. You can contact their technical support by email at support@opennix.ru. Support engineers are available on business days from 9 am to 6 pm GMT+3.
| Resource type | Quantity |
|---|---|
| Access rights for folder | 2 |
| Object Storage bucket | 1 |
| Lockbox secret version | 1 |
| Lockbox secret | 1 |
| Service account static access key | 1 |
| Service account | 1 |
| PostgreSQL database | 1 |
| PostgreSQL user | 1 |
| PostgreSQL cluster | 1 |
| Virtual machines | 4 |
| Network Load Balancer | 1 |
| NLB target group | 1 |
| Redis cluster | 1 |
| VPC security group | 1 |