Skip to main content

Installation

The pgstack CLI scaffolds a new project with Docker Compose, an .env file, and an initial migration directory.

npx pgstack init my-project
cd my-project
# .env is generated for you with random secrets — no copy step needed
docker compose up -d

What gets created:

my-project/
├── docker-compose.yml # All-in-one dev stack (pre-built image)
├── .env # Generated secrets — do not commit
├── .env.example # Variable reference (no secrets)
├── pgstack.config.json # Project config (ports, schemas)
├── .gitignore
├── migrations/
│ └── 00000000000000_pgstack_init.sql # pgStack security bootstrap
├── seed.sql # Optional seed data
├── public/
│ └── index.html # Starter page served by the proxy
└── types/ # Output dir for `pgstack generate types`

Option 2: Docker Compose from source

Clone the repository and run the dev stack:

git clone https://github.com/ndokutovich/pg_reactive.git
cd pg_reactive
cp .env.example .env
docker compose up -d --build

The --build flag compiles the PostgreSQL extension and the proxy from source. This takes 2-5 minutes the first time. Subsequent builds are cached.

Option 3: Pre-built images (production)

Use the production compose file with pre-built images from GitHub Container Registry:

curl -O https://raw.githubusercontent.com/ndokutovich/pg_reactive/master/docker-compose.prod.yml
curl -O https://raw.githubusercontent.com/ndokutovich/pg_reactive/master/.env.example
cp .env.example .env
# Fill in all required secrets (see below)
docker compose -f docker-compose.prod.yml up -d

System requirements

ComponentMinimumRecommended
RAM512 MB2 GB
CPU1 core2+ cores
Disk1 GB10 GB+
Docker20.10+latest
Node.js (CLI only)18.0+20 LTS

Environment variables

Copy .env.example to .env and fill in the values before starting in production.

Required for production

VariableTypeDescription
POSTGRES_PASSWORDstringPostgreSQL superuser password. Use a strong random value.
JWT_SECRETstringSecret key for signing JWT access tokens. Must be 32+ characters. Generate with openssl rand -base64 32.
ANON_KEYstringOpaque API key for anonymous (unauthenticated) requests. Generate with openssl rand -base64 32.
SERVICE_ROLE_KEYstringOpaque API key for service-role (admin, bypasses RLS) requests. Keep secret.
SECRETS_ENCRYPTION_KEYstringAES-256 key for at-rest encryption of edge-function env vars and webhook signing secrets. 32 raw bytes, base64-encoded. Generate with openssl rand -base64 32.
SITE_URLstringYour application's public URL. Required for OAuth redirect callbacks. Example: https://app.example.com.

Database

VariableDefaultDescription
POSTGRES_DBappDatabase name.
POSTGRES_USERpostgresPostgreSQL superuser username.

OAuth providers (optional)

VariableDescription
GOOGLE_CLIENT_IDGoogle OAuth client ID from Google Cloud Console.
GOOGLE_CLIENT_SECRETGoogle OAuth client secret.
GITHUB_CLIENT_IDGitHub OAuth App client ID from GitHub Settings.
GITHUB_CLIENT_SECRETGitHub OAuth App client secret.

SMTP / email (optional)

Required for email verification and magic links.

VariableDefaultDescription
SMTP_HOSTSMTP server hostname.
SMTP_PORT587SMTP port.
SMTP_USERSMTP authentication username.
SMTP_PASSSMTP authentication password.
SMTP_FROMnoreply@pgstack.localFrom address for emails sent by pgStack.

Tuning

VariableDefaultDescription
PROXY_PORT8080Port the proxy listens on.
MAX_CONNECTIONS10000Maximum concurrent WebSocket connections.
ACCESS_TOKEN_EXPIRY_SECS3600Access token lifetime in seconds (1 hour).
REFRESH_TOKEN_EXPIRY_DAYS30Refresh token lifetime in days.
MAX_BODY_SIZE1048576Maximum request body size in bytes (1 MB).
ALLOWED_ORIGINS`` (strict)CORS / WebSocket origin allow-list, comma-separated. Example: https://app.example.com,https://admin.example.com. Empty = strict: no Access-Control-Allow-Origin header is emitted; cross-origin browsers are blocked; same-origin and non-browser clients still work.
ALLOW_PERMISSIVE_CORSfalseWhen ALLOWED_ORIGINS is empty AND this is true, the proxy reflects any Origin with Access-Control-Allow-Credentials: true (the old dev behavior). Never enable on a public host.
REQUIRE_AUTHENTICATED_WSfalseWhen true, anon-key WebSocket subscriptions are rejected — only real JWTs (authenticated / service_role) may open /ws/{query_id}. Recommended whenever live queries return user-scoped data.
READ_TIMEOUT30HTTP read timeout in seconds.
WRITE_TIMEOUT60HTTP write timeout in seconds.
IDLE_TIMEOUT120HTTP keep-alive idle timeout in seconds.
GZIP_LEVEL5Gzip compression level (0=disabled, 1-9).
PG_POOL_MAX_CONNS50Max connections in the proxy's PostgreSQL pool.
PG_POOL_MIN_CONNS5Minimum idle connections kept warm.
TLS_MODEoffoff (reverse proxy), auto (Let's Encrypt), manual (own certs).

See Environment Variables for the complete list including TLS certificate settings.

Advanced PostgreSQL

VariableDescription
pg_reactive.max_subscriptionsMaximum concurrent live query subscriptions (shared memory slots). Default: 1024. Requires PostgreSQL restart. Set via docker-compose.yml command: flag.

Generating secrets

# JWT_SECRET, ANON_KEY, SERVICE_ROLE_KEY, POSTGRES_PASSWORD, SECRETS_ENCRYPTION_KEY
openssl rand -base64 32

Run this five times and use each output for the respective variable.

Verifying the installation

# Check both containers are healthy
docker compose ps

# Check the proxy responds
curl http://127.0.0.1:8080/health
# {"status":"ok","pg_connected":true}

# Check PostgreSQL is running
docker compose exec db psql -U postgres -c "SELECT version();"

Updating pgStack

Dev (source build)

git pull
docker compose up -d --build

Production (pre-built images)

docker compose -f docker-compose.prod.yml pull
docker compose -f docker-compose.prod.yml up -d

In the production stack (docker-compose.prod.yml) and CLI-scaffolded projects, PostgreSQL data lives in the named volume pgdata and survives restarts and image updates. The dev source-build stack stores data on tmpfs — it is wiped on every container restart by design.

Upgrading the PostgreSQL extension

Pulling a new image does not upgrade an already-installed extension — the installed version lives in the database (the pgdata volume). After updating the images, apply pending extension migrations:

docker compose exec db psql -U postgres -c "ALTER EXTENSION pg_reactive UPDATE;"

Check the installed version:

docker compose exec db psql -U postgres -c \
"SELECT extversion FROM pg_extension WHERE extname = 'pg_reactive';"

Upgrade scripts are cumulative (for example 0.1.2 → 0.1.3 → 0.1.4); ALTER EXTENSION ... UPDATE chains them automatically.