Database
FluxSend uses PostgreSQL as its primary database. The application manages its own schema via migrations on every startup — no manual migration steps are needed.
Requirements
| Requirement | Version |
|---|---|
| PostgreSQL | 13+ (16 recommended for development) |
pgcrypto extension |
Required (enabled automatically by migrations) |
The pgcrypto extension is used for gen_random_uuid() (UUID primary keys) and crypt() (API key bcrypt comparison in queries).
Environment variables
| Variable | Description | Default | Required |
|---|---|---|---|
DB_HOST |
PostgreSQL hostname | — | Yes |
POSTGRES_USER |
Database user | — | Yes |
POSTGRES_PASSWORD |
Database password | — | Yes |
POSTGRES_DB |
Database name | — | Yes |
Connection string
The connection string is built automatically at startup:
postgres://{POSTGRES_USER}:{POSTGRES_PASSWORD}@{DB_HOST}:5432/{POSTGRES_DB}?sslmode=disable
- Port: Hardcoded to
5432(not configurable) - SSL mode: Hardcoded to
disable(not configurable)
Dev defaults (from .env)
DB_HOST=localhost
POSTGRES_USER=devuser
POSTGRES_PASSWORD=devpass
POSTGRES_DB=devdb
Docker Compose (development)
The docker-compose.yaml provides a PostgreSQL 16 service:
dev-postgres:
image: postgres:16
container_name: dev-postgres
environment:
- POSTGRES_USER=${POSTGRES_USER}
- POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
- POSTGRES_DB=${POSTGRES_DB}
ports:
- "5432:5432"
restart: unless-stopped
Start it:
make db
Stop it:
make db-down
Note: The dev PostgreSQL container has no persistent volume. Data is lost when the container is removed.
Migration system
Migrations run automatically on every startup — there is no manual migrate up step.
Library
github.com/golang-migrate/migrate/v4 v4.19.1 with the file source driver and postgres database driver.
Migration files
Located at internal/repo/migrations/. Each migration has an up file (.up.sql) and optionally a down file (.down.sql).
| Migration | Description |
|---|---|
000001_init_schema |
Creates users, files, shares tables |
000002_private_downloads |
Adds private_download_token to files |
000003_delete_file_constraint |
Drops old uniqueness constraint |
000004_new_file_constraint |
Adds files_unique constraint |
000005_notes_table |
Creates notes table |
000006_unique_user_file_note_constraint |
Adds unique constraint on notes |
000007_unique_file_name_per_owner |
Deduplicates files, adds per-owner unique name |
000008_received_seen_at |
Adds received_seen_at to shares |
000009_idx_public_shares |
Creates unique index on public shares |
000010_uuid_user_id |
Migrates user PK from google_id (TEXT) to UUID |
000011_add_storage_mapping_uuid |
Adds storage_mapping UUID to files |
000012_identities_table |
Creates identities and sessions tables, refactors users |
000013_cleanup_old_constraints |
Drops and recreates foreign keys |
000014_user_plans |
Creates plans and plan_features, seeds free/developer/enterprise plans |
000015_files_created_at |
Adds created_at to files |
000016_share_password_protected |
Adds password_hash to shares |
000017_share_failed_attempts |
Adds failed_attempts to shares |
000018_workspaces |
Creates workspaces, workspace_members, workspace_invites |
000019_workspace_admin_role |
Adds admin role to workspace enums |
000020_workspace_files |
Creates workspace_files table |
000021_workspace_plans |
Adds workspace quota columns to plans |
000022_api_keys |
Creates api_keys, api_key_scopes, api_key_user_assignments, api_key_workspaces |
000023_api_key_plan_limits |
Adds API key limit columns to plans |
Tables
After all migrations, the schema contains:
users— user accountsfiles— file metadatashares— sharing records (private, public, password-protected)notes— file notesidentities— OAuth provider identities (Google, GitHub)sessions— authentication sessionsplans— plan definitions with quota limitsplan_features— per-plan feature flagsworkspaces— collaborative workspacesworkspace_members— workspace membership and rolesworkspace_invites— pending workspace invitationsworkspace_files— workspace file records with pathsapi_keys— API keys with bcrypt hashesapi_key_scopes— permissions per API keyapi_key_user_assignments— private key to user bindingsapi_key_workspaces— workspace key to workspace bindingsusers_old— legacy table, retained after UUID migration
Custom migration
In addition to SQL files, a Go-based migration BackfillPrivateDownloadTokens() runs on every startup. It:
- Finds files missing a
private_download_token - Generates and assigns one using
pkg.GenerateSecureTokenFromID() - Adds a
NOT NULLconstraint andUNIQUEindex on the column if absent
Connection pool
The application uses Go's database/sql defaults (no custom pool configuration):
| Setting | Default | Implication |
|---|---|---|
MaxOpenConns |
0 (unlimited) | No cap on concurrent connections |
MaxIdleConns |
2 | Only 2 idle connections kept |
ConnMaxLifetime |
0 (unlimited) | Connections never recycled |
ConnMaxIdleTime |
0 (unlimited) | Idle connections kept forever |
Pool settings are not configurable via environment variables.
sqlc (code generation)
SQL queries are defined in internal/repo/query/ and compiled to type-safe Go code in internal/repo/sqlc/.
Configuration (sqlc.yaml):
version: "2"
sql:
- engine: "postgresql"
schema: "./internal/repo/migrations/"
queries: "./internal/repo/query/"
gen:
go:
package: "sqlc"
emit_json_tags: true
emit_interface: true
out: "./internal/repo/sqlc"
Regenerate after changing queries or schema:
sqlc generate
Query files
| File | Queries |
|---|---|
users.sql |
7 — create, get by ID/email, get/update bucket, delete |
files.sql |
11 — insert, get by owner/name/id/token, delete, rename |
shares.sql |
16 — insert (public/private/password-protected), get by token, mark seen, delete, count unseen, increment failed attempts |
notes.sql |
2 — upsert note, get note |
sessions.sql |
3 — create, get (with expiry), delete |
identities.sql |
3 — get by provider, create, get by user |
plans.sql |
11 — get user+plan, quota checks, user stats, daily usage |
private_downloads.sql |
3 — list missing tokens, update, get by name |
workspaces.sql |
26 — CRUD, members, invites, quota checks |
workspace_files.sql |
14 — CRUD, folder management, move, uploader info |
api_keys.sql |
10 — create, assign (private/workspace), list, revoke, scope lookup, quota check, bcrypt auth lookup |
Startup behavior
On every startup the application:
- Reads DB env vars — panics if
DB_HOST,POSTGRES_USER,POSTGRES_PASSWORD, orPOSTGRES_DBare empty - Opens a connection with
sql.Open("postgres", connString) - Runs all pending migrations via
golang-migrate(Up()) - Runs the
BackfillPrivateDownloadTokenscustom migration - Proceeds with server startup
There is no retry logic — if the database is unreachable at startup, the application exits with a fatal error.