> ## Documentation Index
> Fetch the complete documentation index at: https://private-7c7dfe99-mintlify-3a82795f.mintlify.site/llms.txt
> Use this file to discover all available pages before exploring further.

# Local development environments

> Run PostgreSQL locally with Docker for fast development against Managed Postgres

export const galaxyOnClick = eventName => () => {
  try {
    if (typeof window !== "undefined" && window.galaxy && eventName) {
      window.galaxy.track(eventName, {
        interaction: "click"
      });
    }
  } catch (e) {}
};

export const BetaBadge = ({link, galaxyTrack, galaxyEvent}) => {
  if (link) {
    return <a href={link} target="_blank" rel="noopener noreferrer" className="betaBadge" onClick={galaxyTrack && galaxyEvent ? galaxyOnClick(galaxyEvent) : undefined}>
                <Icon />
                <span>Beta</span>
            </a>;
  }
  return <div className="betaBadge">
            <Icon />
            <span>
                Beta feature. 
                <u>
                    <a href="/docs/beta-and-experimental-features#beta-features">
                        Learn more.
                    </a>
                </u>
            </span>
        </div>;
};

Managed Postgres is built on standard PostgreSQL and works with the existing PostgreSQL ecosystem. For most development tasks, you can develop and test against a local PostgreSQL instance running in Docker rather than a cloud deployment.

This approach provides a fast feedback loop, simplifies onboarding, reduces dependencies on shared infrastructure, and lets you experiment safely without impacting production systems.

The goal isn't to replicate production exactly. Instead, create a reproducible local environment that:

* Uses the same PostgreSQL major version as production.
* Applies the same schema definitions as production.
* Contains representative development data.
* Supports normal application development and testing workflows.

Because Managed Postgres is standard PostgreSQL, existing migration frameworks, schema management tools, and data seeding approaches work without modification.

<h2 id="example-development-flow">
  Example development flow
</h2>

A typical local development workflow looks like:

```mermaid theme={null}
graph LR
    A[Apply the application<br/>schema] --> B[Local PostgreSQL in Docker] --> C[Apply schema]
```

```mermaid theme={null}
graph LR
    A[Restore sanitized tenant<br/>dataset] -.-> B[Generate seed data] -.-> C[Develop and test locally]
```

Managed Postgres fits into existing PostgreSQL development workflows. By developing against a local PostgreSQL instance, teams can iterate quickly, maintain reproducible environments, and gain confidence that applications behave consistently when deployed to Managed Postgres.

<h2 id="run-postgresql-locally-with-docker">
  Run PostgreSQL locally with Docker
</h2>

The simplest way to create a local development environment is to run PostgreSQL in Docker.

Choose a PostgreSQL version that matches your Managed Postgres deployment:

```yaml docker-compose.yml theme={null}
services:
  postgres:
    image: postgres:18
    container_name: local-postgres
    restart: unless-stopped

    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: app

    ports:
      - "15432:5432"

    volumes:
      - postgres_data:/var/lib/postgresql

volumes:
  postgres_data:
```

Start PostgreSQL:

```bash theme={null}
docker compose up -d
```

Verify connectivity:

```bash theme={null}
psql -h localhost -U postgres -p 15432 -d app
```

At this point PostgreSQL is running locally but doesn't yet contain the application schema or any development data.

<h2 id="apply-the-application-schema">
  Apply the application schema
</h2>

There's no single required approach for creating the schema in a local environment. Most organizations already have an established schema management workflow that can be reused unchanged.

<h3 id="application-migrations">
  Application migrations
</h3>

Many teams use the same migration framework that runs in staging and production environments — tools like Flyway, Liquibase, Rails migrations, Django migrations, Prisma migrations, or Alembic.

Applying migrations locally ensures schema evolution is continuously tested as part of normal development:

```bash theme={null}
./migrate up
# or
npm run migrate
# or
rails db:migrate
```

<h3 id="schema-only-postgresql-dumps">
  Schema-only PostgreSQL dumps
</h3>

A schema-only PostgreSQL export can reproduce an existing database structure. This is useful for onboarding, investigating schema behavior, validating compatibility, or quickly bootstrapping development environments.

Export the schema:

```bash theme={null}
pg_dump \
  --schema-only \
  --no-owner \
  --no-privileges \
  -h <host> \
  -U <user> \
  -d <database> \
  > schema.sql
```

Restore locally:

```bash theme={null}
psql \
  -h localhost \
  -U postgres \
  -p 15432    \
  -d app \
  -f schema.sql
```

<h3 id="checked-in-sql-definitions">
  Checked-in SQL definitions
</h3>

Some teams maintain schema definitions directly in source control as SQL files. These can be applied directly to a local PostgreSQL instance during environment setup.

Regardless of the approach, the important outcome is that schema creation is automated, reproducible, and derived from version-controlled definitions.

<h2 id="populate-representative-development-data">
  Populate representative development data
</h2>

Once the schema exists, populate the database with representative development data.

For most development workflows, synthetic datasets generated through seed scripts are sufficient. They're easy to recreate, safe to distribute, and avoid the compliance and security considerations associated with production data.

A common approach for SaaS applications is to generate data for a small number of sample tenants and create realistic relationships between users, products, orders, and other business entities.

<h3 id="example-multi-tenant-schema">
  Example multi-tenant schema
</h3>

The following schema represents a simplified multi-tenant SaaS application:

```sql theme={null}
CREATE TABLE tenants (
    id UUID PRIMARY KEY,
    name TEXT NOT NULL
);

CREATE TABLE users (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    email TEXT NOT NULL,
    first_name TEXT,
    last_name TEXT,
    created_at TIMESTAMP DEFAULT now()
);

CREATE TABLE products (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    name TEXT NOT NULL,
    price NUMERIC(10,2)
);

CREATE TABLE orders (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    user_id UUID NOT NULL REFERENCES users(id),
    status TEXT,
    created_at TIMESTAMP DEFAULT now()
);

CREATE TABLE order_items (
    id UUID PRIMARY KEY,
    order_id UUID NOT NULL REFERENCES orders(id),
    product_id UUID NOT NULL REFERENCES products(id),
    quantity INTEGER
);

CREATE TABLE audit_logs (
    id UUID PRIMARY KEY,
    tenant_id UUID NOT NULL REFERENCES tenants(id),
    entity_type TEXT,
    entity_id UUID,
    action TEXT,
    created_at TIMESTAMP DEFAULT now()
);
```

<h3 id="generate-sample-data">
  Generate sample data
</h3>

Install dependencies:

```bash theme={null}
pip install faker psycopg2-binary
```

Create a file named `seed.py`:

```python seed.py theme={null}
import random
import uuid

import psycopg2
from faker import Faker

fake = Faker()

conn = psycopg2.connect(
    host="localhost",
    port=15432,
    dbname="app",
    user="postgres",
    password="postgres"
)

cur = conn.cursor()

tenant_ids = []

for tenant_name in [
    "Tenant A",
    "Tenant B",
    "Tenant C"
]:
    tenant_id = str(uuid.uuid4())
    tenant_ids.append(tenant_id)

    cur.execute(
        """
        INSERT INTO tenants(id, name)
        VALUES (%s, %s)
        """,
        (tenant_id, tenant_name)
    )

for tenant_id in tenant_ids:

    users = []
    products = []

    for _ in range(20):
        user_id = str(uuid.uuid4())
        users.append(user_id)

        cur.execute(
            """
            INSERT INTO users(
                id,
                tenant_id,
                email,
                first_name,
                last_name
            )
            VALUES (%s,%s,%s,%s,%s)
            """,
            (
                user_id,
                tenant_id,
                fake.email(),
                fake.first_name(),
                fake.last_name()
            )
        )

    for _ in range(15):
        product_id = str(uuid.uuid4())
        products.append(product_id)

        cur.execute(
            """
            INSERT INTO products(
                id,
                tenant_id,
                name,
                price
            )
            VALUES (%s,%s,%s,%s)
            """,
            (
                product_id,
                tenant_id,
                fake.word(),
                round(random.uniform(10, 500), 2)
            )
        )

    for _ in range(50):

        order_id = str(uuid.uuid4())

        cur.execute(
            """
            INSERT INTO orders(
                id,
                tenant_id,
                user_id,
                status
            )
            VALUES (%s,%s,%s,%s)
            """,
            (
                order_id,
                tenant_id,
                random.choice(users),
                random.choice([
                    "pending",
                    "completed",
                    "cancelled"
                ])
            )
        )

        for _ in range(random.randint(1, 5)):
            cur.execute(
                """
                INSERT INTO order_items(
                    id,
                    order_id,
                    product_id,
                    quantity
                )
                VALUES (%s,%s,%s,%s)
                """,
                (
                    str(uuid.uuid4()),
                    order_id,
                    random.choice(products),
                    random.randint(1, 10)
                )
            )

        cur.execute(
            """
            INSERT INTO audit_logs(
                id,
                tenant_id,
                entity_type,
                entity_id,
                action
            )
            VALUES (%s,%s,%s,%s,%s)
            """,
            (
                str(uuid.uuid4()),
                tenant_id,
                "order",
                order_id,
                "created"
            )
        )

conn.commit()
conn.close()
```

Run the script:

```bash theme={null}
python seed.py
```

The resulting dataset contains:

| Table        | Records |
| ------------ | ------- |
| tenants      | 3       |
| users        | 60      |
| products     | 45      |
| orders       | 150     |
| order\_items | 400+    |
| audit\_logs  | 150+    |

This dataset is large enough to exercise common application workflows, tenant isolation logic, reporting queries, and relational integrity checks while remaining lightweight for local development and testing.

<h2 id="postgresql-clickhouse-development-environment">
  PostgreSQL + ClickHouse development environment
</h2>

The examples above focus on local PostgreSQL development. If you want to test the complete PostgreSQL-to-ClickHouse architecture locally, you can run the open-source PostgreSQL + ClickHouse stack.

This stack combines PostgreSQL for transactional workloads, ClickHouse for analytics, and PeerDB for native change data capture (CDC). It lets you develop against PostgreSQL while continuously replicating data into ClickHouse — making it possible to test operational analytics, reporting workloads, and real-time data pipelines directly from your laptop.

The stack can be started with a single command and includes all required services preconfigured:

```bash theme={null}
git clone https://github.com/ClickHouse/postgres-clickhouse-stack.git
cd postgres-clickhouse-stack

./run.sh start
```

The stack includes:

* PostgreSQL
* ClickHouse
* PeerDB for PostgreSQL CDC
* Supporting services and sample applications

For setup instructions, architecture details, and a walkthrough of the complete stack, see:

* [Blog: PostgreSQL + ClickHouse OSS](https://clickhouse.com/blog/postgres-clickhouse-oss)
* [GitHub: postgres-clickhouse-stack](https://github.com/ClickHouse/postgres-clickhouse-stack)

This is a useful next step once your application runs locally against PostgreSQL and you want to validate PostgreSQL-to-ClickHouse synchronization, real-time analytics, and end-to-end application behavior.
