Skip to main content

Quickstart

This guide walks you through the basic pgtofu workflow: extracting your current schema, defining your desired schema, and generating migrations.

Prerequisites

  • A running PostgreSQL database (local or remote)
  • Docker installed (or Go 1.25+ for building from source)
  • golang-migrate for applying migrations

Step 1: Install pgtofu

The quickest way to get started is with Docker:
docker pull accented/pgtofu:latest
go install github.com/accented-ai/pgtofu/cmd/pgtofu@latest

Step 2: Set Up Your Database Connection

export DATABASE_URL="postgres://user:password@localhost:5432/mydb"
Never commit database credentials to version control. Use environment variables or a secrets manager.

Step 3: Extract Current Schema

Extract your database’s current schema to a JSON file:
docker run --rm \
  -v "$(pwd):/workspace" \
  -w /workspace \
  -e DATABASE_URL="$DATABASE_URL" \
  accented/pgtofu:latest extract \
  --output current-schema.json
This creates a current-schema.json file containing a complete representation of your database schema.

Step 4: Define Your Desired Schema

Create a schema directory and define your desired schema using standard SQL files:
mkdir -p schema/tables schema/functions
Create schema/tables/users.sql:
CREATE TABLE users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(100),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_users_email ON users(email);
CREATE INDEX idx_users_created_at ON users(created_at);
Create schema/tables/orders.sql:
CREATE TABLE orders (
    id BIGSERIAL PRIMARY KEY,
    user_id BIGINT NOT NULL REFERENCES users(id) ON DELETE CASCADE,
    total NUMERIC(12, 2) NOT NULL,
    status VARCHAR(20) NOT NULL DEFAULT 'pending',
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX idx_orders_user_id ON orders(user_id);
CREATE INDEX idx_orders_status ON orders(status);
Organize your SQL files by type (tables, views, functions, etc.) for maintainability. pgtofu will automatically resolve dependencies regardless of file organization.

Step 5: Preview Changes

Compare your current schema with the desired schema to see what changes will be made:
docker run --rm \
  -v "$(pwd):/workspace" \
  -w /workspace \
  accented/pgtofu:latest diff \
  --current current-schema.json \
  --desired ./schema
Example output:
Schema Comparison Summary
========================

Changes detected: 4

SAFE Changes:
  - ADD_TABLE: Add table: public.users
  - ADD_TABLE: Add table: public.orders
  - ADD_INDEX: Add index: public.idx_users_email
  - ADD_INDEX: Add index: public.idx_orders_user_id

No breaking changes detected.

Step 6: Generate Migrations

Generate migration files that can be applied with golang-migrate:
docker run --rm \
  -v "$(pwd):/workspace" \
  -w /workspace \
  accented/pgtofu:latest generate \
  --current current-schema.json \
  --desired ./schema \
  --output-dir ./migrations
This creates migration files in the migrations directory:
migrations/
├── 000001_add_users_table.up.sql
├── 000001_add_users_table.down.sql
├── 000002_add_orders_table.up.sql
└── 000002_add_orders_table.down.sql
000001_add_users_table.up.sql:
-- Migration generated by pgtofu
-- Changes: ADD_TABLE (public.users)

BEGIN;

CREATE TABLE IF NOT EXISTS public.users (
    id BIGSERIAL PRIMARY KEY,
    email VARCHAR(255) NOT NULL UNIQUE,
    name VARCHAR(100),
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
    updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
);

CREATE INDEX IF NOT EXISTS idx_users_email ON public.users(email);
CREATE INDEX IF NOT EXISTS idx_users_created_at ON public.users(created_at);

COMMIT;

Step 7: Apply Migrations

Use golang-migrate to apply the generated migrations:
migrate -path ./migrations -database "$DATABASE_URL" up

Step 8: Iterate

As your application evolves, repeat the workflow:
  1. Update your SQL files in schema/
  2. Re-extract the current schema: pgtofu extract --output current-schema.json
  3. Preview changes: pgtofu diff --current current-schema.json --desired ./schema
  4. Generate new migrations: pgtofu generate --current current-schema.json --desired ./schema
  5. Apply: migrate -path ./migrations -database "$DATABASE_URL" up
Use --preview with the generate command to see what migrations would be created without writing files.

Next Steps