fix: bundle Postgres so the service can actually start

The template pointed PS_DATA_SOURCE_URI at a placeholder connection
string (postgresql://user:password@host:5432/db), so the PowerSync
service could never connect and crash-looped, leaving the domain
returning Traefik 404s.

Changes:
- Add a bundled Postgres 17 service with wal_level=logical, a
  healthcheck, and a persistent volume; powersync waits for it to be
  healthy before starting.
- Replace the base64-encoded config env var with a readable
  powersync.yaml mounted via [[config.mounts]]. Replication reads from
  the powersync database and bucket storage uses a separate
  powersync_storage database (Postgres storage requires a database
  distinct from the replication source).
- Add an init SQL mount that creates the storage database, a demo
  "lists" table synced by the default sync rules, and the powersync
  publication required for logical replication.
- Replace the unsupported sync_config block with standard sync_rules,
  and add an HS256 dev key so client auth works out of the box.
- Remove host port mapping (Traefik routes to the container port) and
  point the template domain at /probes/liveness since PowerSync has no
  route on "/".

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Mauricio Siu
2026-07-08 02:05:00 -06:00
parent 947f099806
commit 8480130401
2 changed files with 104 additions and 25 deletions

View File

@@ -1,15 +1,33 @@
services:
postgres:
image: postgres:17-alpine
restart: unless-stopped
command: ["postgres", "-c", "wal_level=logical"]
environment:
POSTGRES_USER: powersync
POSTGRES_PASSWORD: ${POSTGRES_PASSWORD}
POSTGRES_DB: powersync
volumes:
- postgres-data:/var/lib/postgresql/data
- ../files/init-powersync.sql:/docker-entrypoint-initdb.d/init-powersync.sql:ro
healthcheck:
test: ["CMD-SHELL", "pg_isready -U powersync -d powersync"]
interval: 5s
timeout: 5s
retries: 12
powersync:
image: journeyapps/powersync-service:1.21.0
restart: unless-stopped
depends_on:
postgres:
condition: service_healthy
environment:
POWERSYNC_CONFIG_B64: ${POWERSYNC_CONFIG_B64}
POWERSYNC_SYNC_CONFIG_B64: ${POWERSYNC_SYNC_CONFIG_B64}
PS_DATA_SOURCE_URI: ${PS_DATA_SOURCE_URI}
PS_PORT: ${PS_PORT:-8080}
PS_JWKS_URL: ${PS_JWKS_URL:-}
PS_ADMIN_TOKEN: ${PS_ADMIN_TOKEN}
NODE_OPTIONS: ${NODE_OPTIONS}
ports:
- "${PS_PORT:-8080}:${PS_PORT:-8080}"
POWERSYNC_CONFIG_PATH: /config/powersync.yaml
NODE_OPTIONS: --max-old-space-size=1000
volumes:
- ../files/powersync.yaml:/config/powersync.yaml:ro
command: ["start", "-r", "unified"]
volumes:
postgres-data:

View File

@@ -1,27 +1,88 @@
[variables]
main_domain = "${domain}"
# Your PostgreSQL connection string. PowerSync connects directly to replicate + sync.
# Must have logical replication enabled (wal_level=logical).
data_source_uri = "postgresql://user:password@host:5432/db?sslmode=require"
# Auto-generated admin token for API access.
# Password for the bundled PostgreSQL (used as replication source and bucket storage).
postgres_password = "${password:32}"
# Auto-generated token for the PowerSync API (used in Authorization header).
admin_token = "${password:32}"
# HS256 secret used to sign development JWTs for clients (base64url encoded).
jwks_key = "${password:48}"
[config]
[[config.domains]]
serviceName = "powersync"
port = 8080
host = "${main_domain}"
# PowerSync has no route on "/"; the liveness probe returns 200.
path = "/probes/liveness"
[config.env]
# PowerSync listens on port 8080 internally. Dokproxy/Traefik handles HTTPS.
PS_PORT = "8080"
# Source + bucket storage URI. PowerSync uses PostgreSQL for both.
PS_DATA_SOURCE_URI = "${data_source_uri}"
# Secret token for the admin API (used in Authorization header).
PS_ADMIN_TOKEN = "${admin_token}"
# NODE_OPTIONS --max-old-space-size=1000 limits memory to ~1GB
NODE_OPTIONS = "--max-old-space-size=1000"
# Base64-encoded PowerSync service config.
# Contains replication, storage, auth, and sync rules — all via !env PS_* vars.
# Decode with: echo '<base64>' | base64 -d
POWERSYNC_CONFIG_B64 = "dGVsZW1ldHJ5OgogIGRpc2FibGVfdGVsZW1ldHJ5X3NoYXJpbmc6IHRydWUKcmVwbGljYXRpb246CiAgY29ubmVjdGlvbnM6CiAgICAtIHR5cGU6IHBvc3RncmVzcWwKICAgICAgdXJpOiAhZW52IFBTX0RBVEFfU09VUkNFX1VSSQogICAgICBzc2xtb2RlOiBkaXNhYmxlCnN0b3JhZ2U6CiAgdHlwZTogcG9zdGdyZXNxbAogIHVyaTogIWVudiBQU19EQVRBX1NPVVJDRV9VUkkKcG9ydDogIWVudiBQU19QT1JUCnN5bmNfY29uZmlnOgogIGNvbnRlbnQ6IHwKICAgIGNvbmZpZzoKICAgICAgZWRpdGlvbjogMwogICAgc3RyZWFtczoKICAgICAgZ2xvYmFsOgogICAgICAgIGF1dG9fc3Vic2NyaWJlOiB0cnVlCiAgICAgICAgcXVlcmllczoKICAgICAgICAgIC0gU0VMRUNUICogRlJPTSBpbmZvcm1hdGlvbl9zY2hlbWEudGFibGVzIFdIRVJFIHRhYmxlX3NjaGVtYSA9ICdwdWJsaWMnCmNsaWVudF9hdXRoOgogIGF1ZGllbmNlOiBbInBvd2Vyc3luYyJdCmFwaToKICB0b2tlbnM6CiAgICAtICFlbnYgUFNfQURNSU5fVE9LRU4Kc3lzdGVtOgogIGxvZ2dpbmc6CiAgICBsZXZlbDogaW5mbwogICAgZm9ybWF0OiB0ZXh0Cg=="
POSTGRES_PASSWORD = "${postgres_password}"
# PowerSync service configuration.
# Docs: https://docs.powersync.com/self-hosting/installation/powersync-service-setup
[[config.mounts]]
filePath = "powersync.yaml"
content = """
telemetry:
disable_telemetry_sharing: true
# Replication source: the bundled Postgres (wal_level=logical).
# Point this at your own database to sync real data.
replication:
connections:
- type: postgresql
uri: postgresql://powersync:${postgres_password}@postgres:5432/powersync
sslmode: disable
# Bucket storage. Must be a different database than the replication source.
storage:
type: postgresql
uri: postgresql://powersync:${postgres_password}@postgres:5432/powersync_storage
sslmode: disable
port: 8080
# Sync rules: syncs the demo "lists" table to all clients.
# Edit these for your own schema: https://docs.powersync.com/usage/sync-rules
sync_rules:
content: |
bucket_definitions:
global:
data:
- SELECT * FROM lists
client_auth:
# HS256 key for signing development tokens.
# See https://docs.powersync.com/installation/authentication-setup/custom
jwks:
keys:
- kty: oct
alg: HS256
kid: powersync-dev
k: ${jwks_key}
audience: ["powersync"]
api:
tokens:
- ${admin_token}
"""
# Runs once on the first Postgres startup (empty data volume).
[[config.mounts]]
filePath = "init-powersync.sql"
content = """
-- Separate database for PowerSync bucket storage.
CREATE DATABASE powersync_storage OWNER powersync;
-- Demo table synced by the default sync rules.
CREATE TABLE lists (
id uuid PRIMARY KEY DEFAULT gen_random_uuid(),
name text NOT NULL,
created_at timestamptz NOT NULL DEFAULT now()
);
INSERT INTO lists (name) VALUES ('Welcome to PowerSync');
-- Publication used by PowerSync logical replication.
CREATE PUBLICATION powersync FOR ALL TABLES;
"""