From 848013040101c82dd42f40e9996e3a76ad65a0dd Mon Sep 17 00:00:00 2001 From: Mauricio Siu Date: Wed, 8 Jul 2026 02:05:00 -0600 Subject: [PATCH] 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 --- blueprints/powersync/docker-compose.yml | 36 +++++++--- blueprints/powersync/template.toml | 93 ++++++++++++++++++++----- 2 files changed, 104 insertions(+), 25 deletions(-) diff --git a/blueprints/powersync/docker-compose.yml b/blueprints/powersync/docker-compose.yml index 02fa6a02..1440c8f9 100644 --- a/blueprints/powersync/docker-compose.yml +++ b/blueprints/powersync/docker-compose.yml @@ -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: diff --git a/blueprints/powersync/template.toml b/blueprints/powersync/template.toml index e574b059..a702a17a 100755 --- a/blueprints/powersync/template.toml +++ b/blueprints/powersync/template.toml @@ -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 -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; +"""