Baserow Installation Guide: Build a No-Code Relational Database via Docker
Set up a self-hosted no-code database hub. Step-by-step tutorial to installing Baserow on a VPS using Docker Compose.
Baserow Installation Guide: Build a No-Code Relational Database via Docker
Baserow is an open-source, no-code relational database platform that serves as a powerful alternative to Airtable. Under the hood, it is built on a modern, robust stack: a Django-based backend, a Nuxt.js-based frontend, PostgreSQL for relational storage, Redis for caching and message brokerage, and Celery for asynchronous task queueing.
When deploying Baserow in a production environment on a Virtual Private Server (VPS), deploying via Docker Compose offers container isolation, easy updates, and robust service orchestration. However, production deployments require careful tuning of the database backend, client upload limits, WebSocket connections, and Cross-Origin Resource Sharing (CORS) configurations.
This guide provides a comprehensive, production-grade guide to deploying Baserow on a VPS using Docker Compose.
1. Architectural Overview and Data Flow
To deploy and maintain Baserow successfully, you must understand how its components interact.
graph TD
Client[Web Browser] -->|HTTP / HTTPS| Proxy[Nginx / Caddy Proxy]
Client -->|WebSocket /ws/| Proxy
Proxy -->|Port 3000| Frontend[Nuxt.js Frontend]
Proxy -->|Port 8000| Backend[Django Backend]
Backend -->|SQL| DB[(PostgreSQL Database)]
Backend -->|Cache / Queue| Cache[(Redis Cache)]
Celery[Celery Worker/Beat] -->|Tasks| Cache
Celery -->|Write SQL| DB
- Nuxt.js Frontend: Runs on Node.js. It renders the user interface and communicates with the Django backend.
- Django Backend: Serves the REST API, executes database operations, and handles authentication.
- PostgreSQL: The single source of truth. Every Baserow table is physically represented as a PostgreSQL table.
- Redis & Celery: Redis acts as the message broker for Celery. Celery handles long-running asynchronous tasks (such as importing large CSV files, recalculating formulas, and sending notifications).
- WebSocket Service: Provides real-time synchronization so multiple users can collaborate on the same table simultaneously.
2. Production-Grade docker-compose.yml
Instead of the simplified "all-in-one" container which bundles PostgreSQL, Redis, and Django together, a production-ready setup separates these concerns. This allows you to scale, backup, and configure each component independently.
Create a project directory /opt/baserow and create the following docker-compose.yml file:
version: '3.8'
services:
db:
image: postgres:15-alpine
container_name: baserow-db
restart: unless-stopped
environment:
POSTGRES_USER: ${POSTGRES_USER:-baserow}
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD:-change_me_secret_password}
POSTGRES_DB: ${POSTGRES_DB:-baserow}
volumes:
- pgdata:/var/lib/postgresql/data
command: >
postgres
-c max_connections=150
-c shared_buffers=512MB
-c work_mem=16MB
-c maintenance_work_mem=128MB
-c effective_cache_size=1536MB
healthcheck:
test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
interval: 10s
timeout: 5s
retries: 5
redis:
image: redis:7-alpine
container_name: baserow-redis
restart: unless-stopped
volumes:
- redisdata:/data
command: redis-server --appendonly yes
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 10s
timeout: 5s
retries: 5
backend:
image: baserow/baserow:1.24.0
container_name: baserow-backend
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
ports:
- "8000:8000"
environment:
# Database config
DATABASE_HOST: db
DATABASE_NAME: ${POSTGRES_DB:-baserow}
DATABASE_USER: ${POSTGRES_USER:-baserow}
DATABASE_PASSWORD: ${POSTGRES_PASSWORD:-change_me_secret_password}
DATABASE_PORT: 5432
# Redis config
REDIS_HOST: redis
REDIS_PORT: 6379
# Security & URLs
SECRET_KEY: ${SECRET_KEY:-generate_a_secure_random_key_here}
BASEROW_JWT_SIGNING_KEY: ${BASEROW_JWT_SIGNING_KEY:-generate_another_secure_key}
PUBLIC_BACKEND_URL: ${PUBLIC_BACKEND_URL:-http://localhost:8000}
PUBLIC_WEB_FRONTEND_URL: ${PUBLIC_WEB_FRONTEND_URL:-http://localhost:3000}
BASEROW_CORS_ALLOWED_ORIGINS: ${BASEROW_CORS_ALLOWED_ORIGINS:-http://localhost:3000}
BASEROW_EXTRA_ALLOWED_HOSTS: ${BASEROW_EXTRA_ALLOWED_HOSTS:-localhost,127.0.0.1}
# Performance & File Limits
BASEROW_AMOUNT_OF_ALLOWED_UPLOAD_BYTES: 104857600 # 100MB
BASEROW_ROW_PAGE_SIZE_LIMIT: 200
# Run commands
BASEROW_TRIGGER_SYNC_TEMPLATES: "true"
volumes:
- backend_media:/baserow/data/media
command: backend
frontend:
image: baserow/baserow:1.24.0
container_name: baserow-frontend
restart: unless-stopped
depends_on:
- backend
ports:
- "3000:3000"
environment:
PUBLIC_BACKEND_URL: ${PUBLIC_BACKEND_URL:-http://localhost:8000}
PUBLIC_WEB_FRONTEND_URL: ${PUBLIC_WEB_FRONTEND_URL:-http://localhost:3000}
command: frontend
celery-worker:
image: baserow/baserow:1.24.0
container_name: baserow-celery-worker
restart: unless-stopped
depends_on:
db:
condition: service_healthy
redis:
condition: service_healthy
environment:
DATABASE_HOST: db
DATABASE_NAME: ${POSTGRES_DB:-baserow}
DATABASE_USER: ${POSTGRES_USER:-baserow}
DATABASE_PASSWORD: ${POSTGRES_PASSWORD:-change_me_secret_password}
DATABASE_PORT: 5432
REDIS_HOST: redis
REDIS_PORT: 6379
SECRET_KEY: ${SECRET_KEY:-generate_a_secure_random_key_here}
PUBLIC_BACKEND_URL: ${PUBLIC_BACKEND_URL:-http://localhost:8000}
volumes:
- backend_media:/baserow/data/media
command: celery-worker
volumes:
pgdata:
driver: local
redisdata:
driver: local
backend_media:
driver: local