Database Migrations
Fluxbase uses a declarative schema management approach for internal platform tables, combined with optional imperative migrations for your application schema.
Overview
Section titled “Overview”Fluxbase provides two ways to manage database schema:
- Declarative Schema (Internal) - Fluxbase platform tables managed automatically via bootstrap + pgschema
- User Migrations (Optional) - Your custom application tables via imperative SQL migration files
graph LR subgraph "Internal Schema (Declarative)" B1[bootstrap.sql] --> B2[pgschema] B2 --> B3[Platform Tables] end
subgraph "User Schema (Imperative)" U1[Migration Files] --> U2[migrations.app] U2 --> U3[Application Tables] end
B3 -.-> DB[(PostgreSQL)] U3 -.-> DBDeclarative Schema (Internal)
Section titled “Declarative Schema (Internal)”Purpose: Platform infrastructure managed automatically by Fluxbase
The internal Fluxbase schema (auth, storage, functions, jobs, etc.) is managed declaratively:
- bootstrap.sql - Creates schemas, extensions, roles, and default privileges (idempotent)
- pgschema - Compares desired schema files to actual database state and applies diffs
Tracking: migrations.declarative_state table stores schema fingerprint
Execution: Automatically applied on server startup
Schema files location: internal/database/schema/schemas/
How It Works
Section titled “How It Works”- On startup, Fluxbase runs
bootstrap.sqlto ensure schemas and roles exist - The pgschema tool compares schema files to the actual database
- Any differences are applied automatically
- The schema fingerprint is stored for drift detection
Benefits
Section titled “Benefits”- No version numbers - Schema is the source of truth
- Automatic drift detection - Can detect if database was modified outside Fluxbase
- Safe by default - Destructive changes require explicit approval
- Works with CI/CD - Schema is applied automatically, no migration commands needed
User Migrations (Optional)
Section titled “User Migrations (Optional)”Purpose: Application-specific schema managed by you
User migrations allow you to add your own custom database schema using traditional imperative migration files.
Tracking: migrations.app table
Execution: Run on startup if DB_USER_MIGRATIONS_PATH is configured
File format: Standard golang-migrate format with .up.sql and .down.sql files
When to Use User Migrations
Section titled “When to Use User Migrations”| Use Declarative (Internal) | Use User Migrations |
|---|---|
| Never (managed by Fluxbase) | Application tables |
| Custom indexes | |
| Data transformations | |
| Business logic triggers | |
| Application-specific RLS policies |
Migration File Format
Section titled “Migration File Format”User migrations follow the standard golang-migrate format:
001_create_users_table.up.sql001_create_users_table.down.sql002_add_timestamps.up.sql002_add_timestamps.down.sqlEach migration has two files:
.up.sql- Applied when migrating forward.down.sql- Applied when rolling back (optional but recommended)
Migration Numbering
Section titled “Migration Numbering”Migrations are executed in numerical order based on the prefix. Best practices:
- Use sequential numbering:
001,002,003, etc. - Zero-pad numbers for proper sorting
- Never reuse or skip numbers
- Never modify a migration that has already been applied
Example Migration
Section titled “Example Migration”001_create_products_table.up.sql:
-- Create products table in public schemaCREATE TABLE IF NOT EXISTS public.products ( id UUID PRIMARY KEY DEFAULT gen_random_uuid(), name TEXT NOT NULL, description TEXT, price DECIMAL(10,2) NOT NULL, created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(), updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW());
-- Add RLS policiesALTER TABLE public.products ENABLE ROW LEVEL SECURITY;
-- Allow all authenticated users to read productsCREATE POLICY "Products are viewable by authenticated users" ON public.products FOR SELECT TO authenticated USING (true);
-- Allow only admins to insert/update/delete productsCREATE POLICY "Products are manageable by admins" ON public.products FOR ALL TO authenticated USING (auth.role() = 'admin') WITH CHECK (auth.role() = 'admin');001_create_products_table.down.sql:
-- Drop the table (this will also drop policies)DROP TABLE IF EXISTS public.products CASCADE;Configuration
Section titled “Configuration”Docker Compose
Section titled “Docker Compose”To enable user migrations in Docker Compose:
- Create a directory for your migrations:
mkdir -p deploy/migrations/user-
Add your migration files to this directory
-
Update
docker-compose.yml:
services: fluxbase: environment: # Enable user migrations DB_USER_MIGRATIONS_PATH: /migrations/user volumes: # Mount migrations directory (read-only) - ./migrations/user:/migrations/user:ro- Restart Fluxbase:
docker compose restart fluxbaseKubernetes (Helm)
Section titled “Kubernetes (Helm)”To enable user migrations in Kubernetes:
- Create a ConfigMap or PVC with your migration files
Option A: Using ConfigMap (for small migrations):
kubectl create configmap user-migrations \ --from-file=migrations/user/ \ -n fluxbaseOption B: Using PVC (recommended for production):
migrationsPersistence: enabled: true size: 100Mi storageClass: "" # Use cluster default
config: database: userMigrationsPath: /migrations/user- Install or upgrade the Helm chart:
helm upgrade --install fluxbase ./deploy/helm/fluxbase \ --namespace fluxbase \ --create-namespace \ -f values.yaml- Copy your migration files to the PVC:
# Find a podPOD_NAME=$(kubectl get pod -n fluxbase -l app.kubernetes.io/name=fluxbase -o jsonpath="{.items[0].metadata.name}")
# Copy migrationskubectl cp migrations/user/ fluxbase/$POD_NAME:/migrations/user/- Restart the deployment:
kubectl rollout restart deployment/fluxbase -n fluxbaseEnvironment Variables
Section titled “Environment Variables”You can configure user migrations via environment variables:
| Variable | Description | Default |
|---|---|---|
DB_USER_MIGRATIONS_PATH | Path to user migrations directory | "" (disabled) |
When DB_USER_MIGRATIONS_PATH is empty or not set, user migrations are skipped.
Startup Flow
Section titled “Startup Flow”When Fluxbase starts, schema is applied in this order:
- Bootstrap SQL - Creates schemas, extensions, roles (idempotent)
- Declarative Schema - Applies internal Fluxbase schema via pgschema
- User Migrations - Applies your custom migrations (if configured)
Migration progress is logged during startup:
INFO Running bootstrap SQL...INFO Bootstrap completed successfullyINFO Applying declarative schema...INFO Schema applied successfullyINFO Running user migrations... path=/migrations/userINFO Migrations applied successfully source=user version=3INFO Database schema management completedLocal Development
Section titled “Local Development”For local development, Fluxbase provides Make commands:
Database Reset Commands
Section titled “Database Reset Commands”# Partial reset - preserves user data in public schemamake db-reset
# Full reset - drops ALL schemas (WARNING: destroys all data)make db-reset-fullAfter a reset, the bootstrap and declarative schema are applied automatically on the next server startup with make dev.
User Migration Commands
Section titled “User Migration Commands”If you have user migrations configured:
# Create new user migrationmake migrate-create name=add_products# Creates: migrations/XXX_add_products.up.sql and .down.sql
# Apply migrationsmake migrate-up
# Rollback last migrationmake migrate-downNote: These commands are for user-provided migrations only. The internal Fluxbase schema is managed declaratively and applied automatically.
Best Practices
Section titled “Best Practices”1. Test Migrations Locally First
Section titled “1. Test Migrations Locally First”Always test migrations in a development environment before applying to production:
# Start local environmentdocker compose up -d
# Check logs for migration successdocker compose logs fluxbase | grep -i migration2. Use Transactions
Section titled “2. Use Transactions”Wrap DDL statements in transactions when possible:
BEGIN;
CREATE TABLE products (...);CREATE INDEX IF NOT EXISTS idx_products_name ON products(name);
COMMIT;3. Make Migrations Idempotent
Section titled “3. Make Migrations Idempotent”Use conditional statements to make migrations safe to re-run:
-- Good: Uses IF NOT EXISTSCREATE TABLE IF NOT EXISTS products (...);
-- Bad: Will fail if table existsCREATE TABLE products (...);4. Add Indexes Concurrently
Section titled “4. Add Indexes Concurrently”For large tables, create indexes without locking:
-- Add indexes concurrently (won't block reads/writes)CREATE INDEX CONCURRENTLY idx_products_category ON products(category);5. Plan for Rollbacks
Section titled “5. Plan for Rollbacks”Always include .down.sql files to support rollback scenarios:
-- down.sql should reverse the up.sql changesDROP INDEX IF EXISTS idx_products_category;DROP TABLE IF EXISTS products;6. Document Complex Migrations
Section titled “6. Document Complex Migrations”Add comments explaining the purpose of complex migrations:
-- Migration: Add full-text search to products-- Author: Your Name-- Date: 2024-01-15-- Reason: Enable product search functionality
ALTER TABLE products ADD COLUMN search_vector tsvector;
CREATE INDEX IF NOT EXISTS idx_products_search ON products USING gin(search_vector);Troubleshooting
Section titled “Troubleshooting”Migration Failed
Section titled “Migration Failed”If a migration fails partway through, check the logs and fix the issue:
-- Connect to databasepsql -h localhost -U fluxbase -d fluxbase
-- Check migration stateSELECT * FROM migrations.app WHERE status = 'failed';
-- After fixing the issue, mark as pending to retryUPDATE migrations.app SET status = 'pending', error_message = '' WHERE name = 'failed_migration';Migration Not Running
Section titled “Migration Not Running”If your migration isn’t being applied:
- Check file naming: Ensure files follow the format
NNN_name.up.sql - Check file location: Verify files are in the configured directory
- Check permissions: Ensure Fluxbase can read the migration files
- Check logs: Look for migration errors in Fluxbase logs
- Check configuration: Verify
DB_USER_MIGRATIONS_PATHis set correctly
Checking Migration Status
Section titled “Checking Migration Status”To see which user migrations have been applied:
-- Check user migrationsSELECT * FROM migrations.app ORDER BY applied_at DESC;Checking Declarative Schema Status
Section titled “Checking Declarative Schema Status”To check the internal declarative schema status:
-- Check declarative stateSELECT * FROM migrations.declarative_state;Or use the admin API:
curl http://localhost:8080/api/v1/admin/internal-schema/statusAdvanced Topics
Section titled “Advanced Topics”Declarative Schema Management API
Section titled “Declarative Schema Management API”Fluxbase provides internal API endpoints for schema management:
| Endpoint | Description |
|---|---|
GET /api/v1/admin/internal-schema/status | Check schema status |
POST /api/v1/admin/internal-schema/plan | Preview pending changes |
POST /api/v1/admin/internal-schema/apply | Apply schema changes |
GET /api/v1/admin/internal-schema/validate | Validate schema for drift |
POST /api/v1/admin/internal-schema/dump | Dump current schema to files |
These endpoints are useful for CI/CD pipelines and manual schema inspection.
Schema Drift Detection
Section titled “Schema Drift Detection”Fluxbase can detect if the database schema has drifted from the expected state:
# Check for drift via APIcurl http://localhost:8080/api/v1/admin/internal-schema/validateIf drift is detected, you can either:
- Apply the declarative schema to update the database
- Dump the current schema to update the schema files
Running Migrations Separately
Section titled “Running Migrations Separately”In production, you may want to run migrations separately from application startup:
- Use the internal schema API to apply changes before deploying
- Or use a separate init container in Kubernetes