TotalBase
Getting Started

Installation

Run TotalBase with Docker against your PostgreSQL 15-18 database.

TotalBase runs as one container. It connects to a PostgreSQL database you provide and serves everything on one port: the admin at /admin/, the Data API at /api/, and the MCP server at /mcp.

Requirements

  • PostgreSQL 15, 16, 17 or 18. 18 is recommended for new installations. TotalBase won't start on any other major version.
  • A database role that is not a superuser. TotalBase can do exactly what its role can do. If you want to create tables from the admin, the role needs CREATE on those schemas, and the simplest way to get that is to make the role the owner of the database.
  • Docker (or any container runtime) to run the server image.

Prepare the database

Create a role and a database it owns. As a PostgreSQL superuser:

CREATE ROLE totalbase LOGIN PASSWORD 'change-me';
CREATE DATABASE acme OWNER totalbase;

On PostgreSQL 15 and later, the public schema belongs to the database owner, so this role can create tables in public right away.

When the server first starts, it creates its own schema, totalbase, and runs its migrations there. It doesn't touch your other schemas. If a schema named totalbase already exists and holds objects TotalBase didn't create, startup stops with TOTALBASE_SCHEMA_CONFLICT and changes nothing.

An existing database works the same way. Your tables are discovered at startup, but none of them is reachable through the API until you expose it.

Configure

TotalBase reads its configuration from environment variables. Only the database connection and the first administrator live there. Everything else is a setting you change later in the admin.

VariableRequiredMeaning
POSTGRES_HOST, POSTGRES_DB, POSTGRES_USER, POSTGRES_PASSWORDyesThe connection. The role in POSTGRES_USER is the upper limit of what TotalBase can do.
POSTGRES_PORTnoDefault 5432.
POSTGRES_SSL, POSTGRES_SSL_CAnooff (default), require or verify-full; a managed PostgreSQL usually wants TLS.
TOTALBASE_MASTER_KEYnoEncrypts stored secrets (SMTP passwords, OAuth client secrets). At least 32 characters. Never stored in the database. Unset: TotalBase generates one on the first start and keeps it in the data directory.
TOTALBASE_DATA_DIRnoWhere TotalBase keeps what it owns on disk - a generated master key (master.key). Default /var/lib/totalbase: mount a volume there.
TOTALBASE_ADMIN_EMAIL, TOTALBASE_ADMIN_PASSWORDnoThe first administrator, created on the first start. Unset: you create it in the browser (see The first administrator).
TOTALBASE_SETUP_CODEnoThe code that first setup asks for, when a program sets it (at least 16 characters). Unset: TotalBase generates one.
TOTALBASE_PUBLIC_URLnoThe address users reach the instance at, for links in emails and OAuth callbacks. The first setup asks for it too (Public URL in Settings). Unset: http://localhost:<port>.
TOTALBASE_PORTnoDefault 3050.
LOG_LEVELnoDefault info.

The master key

You don't have to create it. On the first start, without TOTALBASE_MASTER_KEY, TotalBase generates a strong key and keeps it in /var/lib/totalbase/master.key, readable by the server only; Status shows where it is. Copy that file somewhere safe, apart from the database backups:

docker compose cp totalbase:/var/lib/totalbase/master.key ./master.key

To set your own instead, put it in TOTALBASE_MASTER_KEY (at least 32 characters; a shorter one stops the start, and the error suggests one). The variable always wins over the file. TotalBase never generates a second key for an installation that already has one: if the file is gone and the variable is unset, it won't start until you restore it.

A full recovery needs both a PostgreSQL backup and the master key. If you lose the key you keep all your data, but every stored secret becomes unreadable and has to be entered again. If you start with a different key, startup fails with INVALID_MASTER_KEY.

The first administrator

Without TOTALBASE_ADMIN_EMAIL and TOTALBASE_ADMIN_PASSWORD, the first start writes a link to its log:

TotalBase needs its first administrator - open http://localhost:3050/admin/setup?code=K7QP-M2XD-9WFA-H4TN
docker compose logs totalbase

Open it - with the port you published, if not 3050 - and fill in your name, email and password, the public URL and the language; you're signed in as the administrator. The code is single-use and only someone with access to the server can read it - also in /var/lib/totalbase/setup-code if the log is gone. Once an administrator exists the setup page is gone. A program (an installer, a hosting panel) can do the same with POST /api/setup and its own TOTALBASE_SETUP_CODE.

With the two variables set, the administrator is created at the first start instead, and there is no setup page. They only matter while no user is an administrator; changing them later doesn't change the existing account. Prefer the setup: the password doesn't stay in the container's environment.

Run with Docker Compose

The image is on Docker Hub, for amd64 and arm64: totalbase/totalbase:beta follows the beta, and totalbase/totalbase:0.1.0-beta.1 pins one version. Pick the example that matches your setup: a new PostgreSQL next to TotalBase, or a PostgreSQL you already run.

From the source code instead, to try a change of your own: docker build --target server -t totalbase:local . from the repository root, then use totalbase:local as the image.

From scratch, PostgreSQL included

This docker-compose.yml starts PostgreSQL 18 and TotalBase together. The init script creates the non-superuser role and the database, as in the Prepare the database section:

docker-compose.yml
services:
  postgres:
    image: postgres:18
    environment:
      POSTGRES_PASSWORD: ${POSTGRES_SUPERUSER_PASSWORD}
      TOTALBASE_DB_PASSWORD: ${TOTALBASE_DB_PASSWORD}   # read by the init script
    volumes:
      - pgdata:/var/lib/postgresql
      - ./postgres-init:/docker-entrypoint-initdb.d:ro
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U postgres -d acme"]
      interval: 5s
      retries: 20

  totalbase:
    image: totalbase/totalbase:beta
    depends_on:
      postgres:
        condition: service_healthy
    environment:
      POSTGRES_HOST: postgres
      POSTGRES_DB: acme
      POSTGRES_USER: totalbase
      POSTGRES_PASSWORD: ${TOTALBASE_DB_PASSWORD}
    volumes:
      - data:/var/lib/totalbase       # uploaded files and the generated master key
    ports:
      - "3050:3050"

volumes:
  pgdata:
  data:
postgres-init/01-totalbase.sh
#!/bin/bash
set -euo pipefail
psql -v ON_ERROR_STOP=1 --username "$POSTGRES_USER" --dbname postgres <<EOSQL
CREATE ROLE totalbase LOGIN PASSWORD '${TOTALBASE_DB_PASSWORD}';
CREATE DATABASE acme OWNER totalbase;
EOSQL

Put the passwords in an .env file next to docker-compose.yml:

.env
POSTGRES_SUPERUSER_PASSWORD=change-me-too
TOTALBASE_DB_PASSWORD=change-me

Then start both services:

docker compose up -d
docker compose logs totalbase   # the link to create the first administrator

Against a PostgreSQL that is already running

With a PostgreSQL of your own (on another server, a managed service, or the host machine), TotalBase is the only service. Create its role and database there first, as in Prepare the database, and point the connection at it:

docker-compose.yml
services:
  totalbase:
    image: totalbase/totalbase:beta
    environment:
      POSTGRES_HOST: db.example.internal    # host.docker.internal for a PostgreSQL on this machine
      POSTGRES_PORT: 5432
      POSTGRES_DB: acme
      POSTGRES_USER: totalbase
      POSTGRES_PASSWORD: ${TOTALBASE_DB_PASSWORD}
      POSTGRES_SSL: require                 # most managed services want TLS; off on a local network
    extra_hosts:
      - "host.docker.internal:host-gateway"   # only for a PostgreSQL on this machine
    volumes:
      - data:/var/lib/totalbase       # uploaded files and the generated master key
    ports:
      - "3050:3050"

volumes:
  data:

The same .env, without POSTGRES_SUPERUSER_PASSWORD. Then docker compose up -d. A PostgreSQL on this machine must accept connections from Docker's network: listen_addresses in postgresql.conf and a host line in pg_hba.conf for the role.

The database may already hold your tables. TotalBase adds only its own totalbase schema; your tables show up as resources, hidden until you expose them.

In both examples the files volume holds uploaded files. Back it up together with the database.

Check that it's running

curl http://localhost:3050/health/ready
# {"status":"ready"}

/health/live answers as soon as the process is up. /health/ready answers once migrations have run and the database is reachable. Use it for a load balancer's health check.

Open the link from the log to create the first administrator (http://localhost:3050/admin/ takes you there too, and asks for the code). With TOTALBASE_ADMIN_EMAIL and TOTALBASE_ADMIN_PASSWORD set, sign in with them instead.

A new instance asks for its first administrator.
A new instance asks for its first administrator.

What happens on startup

Each start runs the same sequence. If any step fails, the process stops before it accepts requests:

  1. Connect to PostgreSQL and check the major version (15-18).
  2. Create the totalbase schema if it doesn't exist, and apply pending migrations, each one in its own transaction.
  3. Check the master key - the variable, else the file in the data directory, else (a new installation only) a new one.
  4. Create the first administrator from the variables if no user is an administrator yet; without them, write the setup link to the log.
  5. Read the PostgreSQL catalog and register every table, view and function as a resource. New resources start hidden.
  6. Start listening, and start the background workers.

Next steps