Skip to content

Commit 7234216

Browse files
committed
feat(postgresql): add multi-version PG builds and Docker Hub publishing
Build PostgreSQL extension packages for both PG 17 and PG 15 in CI. Add Dockerfile.release and Dockerfile.supabase.release for end-users to create Docker images from pre-built GitHub release binaries without cloning the repo. Add docker-publish CI job to push multi-arch images to Docker Hub (sqlitecloud/sqlite-sync-postgres and sqlitecloud/sqlite-sync-supabase) on release.
1 parent d20716a commit 7234216

File tree

4 files changed

+268
-37
lines changed

4 files changed

+268
-37
lines changed

.github/workflows/main.yml

Lines changed: 114 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -277,7 +277,7 @@ jobs:
277277
278278
postgres-build:
279279
runs-on: ${{ matrix.os }}
280-
name: postgresql-${{ matrix.name }}-${{ matrix.arch }} build
280+
name: postgresql${{ matrix.postgres_version }}-${{ matrix.name }}-${{ matrix.arch }} build
281281
timeout-minutes: 15
282282
strategy:
283283
fail-fast: false
@@ -286,15 +286,35 @@ jobs:
286286
- os: ubuntu-22.04
287287
arch: x86_64
288288
name: linux
289+
postgres_version: '17'
290+
- os: ubuntu-22.04
291+
arch: x86_64
292+
name: linux
293+
postgres_version: '15'
294+
- os: ubuntu-22.04-arm
295+
arch: arm64
296+
name: linux
297+
postgres_version: '17'
289298
- os: ubuntu-22.04-arm
290299
arch: arm64
291300
name: linux
301+
postgres_version: '15'
292302
- os: macos-15
293303
arch: arm64
294304
name: macos
305+
postgres_version: '17'
306+
- os: macos-15
307+
arch: arm64
308+
name: macos
309+
postgres_version: '15'
295310
- os: macos-15
296311
arch: x86_64
297312
name: macos
313+
postgres_version: '17'
314+
- os: macos-15
315+
arch: x86_64
316+
name: macos
317+
postgres_version: '15'
298318
steps:
299319

300320
- uses: actions/checkout@v4.2.2
@@ -307,23 +327,23 @@ jobs:
307327
sudo sh -c 'echo "deb http://apt.postgresql.org/pub/repos/apt $(lsb_release -cs)-pgdg main" > /etc/apt/sources.list.d/pgdg.list'
308328
curl -fsSL https://www.postgresql.org/media/keys/ACCC4CF8.asc | sudo gpg --dearmor -o /etc/apt/trusted.gpg.d/postgresql.gpg
309329
sudo apt-get update
310-
sudo apt-get install -y postgresql-server-dev-17
330+
sudo apt-get install -y postgresql-server-dev-${{ matrix.postgres_version }}
311331
312332
- name: macos install postgresql
313333
if: matrix.name == 'macos'
314-
run: brew install postgresql@17 gettext
334+
run: brew install postgresql@${{ matrix.postgres_version }} gettext
315335

316336
- name: build and package postgresql extension (linux)
317337
if: matrix.name == 'linux'
318-
run: make postgres-package
338+
run: make postgres-package PG_CONFIG=/usr/lib/postgresql/${{ matrix.postgres_version }}/bin/pg_config
319339

320340
- name: build and package postgresql extension (macos)
321341
if: matrix.name == 'macos'
322-
run: make postgres-package PG_CONFIG=$(brew --prefix postgresql@17)/bin/pg_config PG_EXTRA_CFLAGS="-I$(brew --prefix gettext)/include ${{ matrix.arch == 'x86_64' && '-arch x86_64' || '' }}"
342+
run: make postgres-package PG_CONFIG=$(brew --prefix postgresql@${{ matrix.postgres_version }})/bin/pg_config PG_EXTRA_CFLAGS="-I$(brew --prefix gettext)/include ${{ matrix.arch == 'x86_64' && '-arch x86_64' || '' }}"
323343

324344
- uses: actions/upload-artifact@v4.6.2
325345
with:
326-
name: cloudsync-postgresql-${{ matrix.name }}-${{ matrix.arch }}
346+
name: cloudsync-postgresql${{ matrix.postgres_version }}-${{ matrix.name }}-${{ matrix.arch }}
327347
path: dist/postgresql/
328348
if-no-files-found: error
329349

@@ -525,6 +545,8 @@ jobs:
525545
[**Expo**](https://www.npmjs.com/package/@sqliteai/sqlite-sync-expo): `npm install @sqliteai/sqlite-sync-expo`
526546
[**Android**](https://central.sonatype.com/artifact/ai.sqlite/sync): `ai.sqlite:sync:${{ steps.tag.outputs.version }}`
527547
[**Swift**](https://github.com/sqliteai/sqlite-sync#swift-package): [Installation Guide](https://github.com/sqliteai/sqlite-sync#swift-package)
548+
[**Docker (PostgreSQL)**](https://hub.docker.com/r/sqlitecloud/sqlite-sync-postgres): `docker pull sqlitecloud/sqlite-sync-postgres:17` or `:15`
549+
[**Docker (Supabase)**](https://hub.docker.com/r/sqlitecloud/sqlite-sync-supabase): `docker pull sqlitecloud/sqlite-sync-supabase:17` or `:15`
528550
529551
---
530552
@@ -534,3 +556,89 @@ jobs:
534556
cloudsync-*-${{ steps.tag.outputs.version }}.*
535557
CloudSync-*-${{ steps.tag.outputs.version }}.*
536558
make_latest: true
559+
560+
docker-publish:
561+
runs-on: ubuntu-22.04
562+
name: docker ${{ matrix.image }} pg${{ matrix.pg_major }}
563+
needs: [release]
564+
if: github.ref == 'refs/heads/main'
565+
566+
env:
567+
DOCKERHUB_ORG: sqlitecloud
568+
569+
strategy:
570+
fail-fast: false
571+
matrix:
572+
include:
573+
- image: sqlite-sync-postgres
574+
pg_major: '17'
575+
dockerfile: docker/postgresql/Dockerfile.release
576+
- image: sqlite-sync-postgres
577+
pg_major: '15'
578+
dockerfile: docker/postgresql/Dockerfile.release
579+
- image: sqlite-sync-supabase
580+
pg_major: '17'
581+
dockerfile: docker/postgresql/Dockerfile.supabase.release
582+
supabase_tag: '17.6.1.071'
583+
- image: sqlite-sync-supabase
584+
pg_major: '15'
585+
dockerfile: docker/postgresql/Dockerfile.supabase.release
586+
supabase_tag: '15.8.1.085'
587+
588+
steps:
589+
590+
- uses: actions/checkout@v4.2.2
591+
with:
592+
submodules: true
593+
594+
- name: get cloudsync version
595+
id: version
596+
run: echo "version=$(make version)" >> $GITHUB_OUTPUT
597+
598+
- uses: docker/setup-qemu-action@v3
599+
600+
- uses: docker/setup-buildx-action@v3
601+
602+
- uses: docker/login-action@v3
603+
with:
604+
username: ${{ secrets.DOCKERHUB_USERNAME }}
605+
password: ${{ secrets.DOCKERHUB_TOKEN }}
606+
607+
- name: set docker tags and build args (standalone)
608+
if: matrix.image == 'sqlite-sync-postgres'
609+
id: standalone
610+
run: |
611+
VERSION=${{ steps.version.outputs.version }}
612+
PG=${{ matrix.pg_major }}
613+
IMAGE=${{ env.DOCKERHUB_ORG }}/${{ matrix.image }}
614+
{
615+
echo "tags=${IMAGE}:${PG},${IMAGE}:${PG}-${VERSION}"
616+
echo "build_args<<EOF"
617+
echo "POSTGRES_TAG=${PG}"
618+
echo "CLOUDSYNC_VERSION=${VERSION}"
619+
echo "EOF"
620+
} >> $GITHUB_OUTPUT
621+
622+
- name: set docker tags and build args (supabase)
623+
if: matrix.image == 'sqlite-sync-supabase'
624+
id: supabase
625+
run: |
626+
VERSION=${{ steps.version.outputs.version }}
627+
IMAGE=${{ env.DOCKERHUB_ORG }}/${{ matrix.image }}
628+
SUPABASE_TAG=${{ matrix.supabase_tag }}
629+
{
630+
echo "tags=${IMAGE}:${{ matrix.pg_major }},${IMAGE}:${{ matrix.pg_major }}-${VERSION},${IMAGE}:${SUPABASE_TAG}"
631+
echo "build_args<<EOF"
632+
echo "SUPABASE_POSTGRES_TAG=${SUPABASE_TAG}"
633+
echo "CLOUDSYNC_VERSION=${VERSION}"
634+
echo "EOF"
635+
} >> $GITHUB_OUTPUT
636+
637+
- uses: docker/build-push-action@v6
638+
with:
639+
context: .
640+
file: ${{ matrix.dockerfile }}
641+
platforms: linux/amd64,linux/arm64
642+
push: true
643+
tags: ${{ matrix.image == 'sqlite-sync-postgres' && steps.standalone.outputs.tags || steps.supabase.outputs.tags }}
644+
build-args: ${{ matrix.image == 'sqlite-sync-postgres' && steps.standalone.outputs.build_args || steps.supabase.outputs.build_args }}
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
# PostgreSQL with pre-compiled CloudSync (sqlite-sync) extension
2+
#
3+
# Usage:
4+
# docker build \
5+
# --build-arg POSTGRES_TAG=17 \
6+
# --build-arg CLOUDSYNC_VERSION=1.0.1 \
7+
# -t sqlite-sync-postgres:17 \
8+
# -f docker/postgresql/Dockerfile.release .
9+
#
10+
# Or pull the pre-built image from Docker Hub:
11+
# docker pull sqlitecloud/sqlite-sync-postgres:17
12+
#
13+
14+
ARG POSTGRES_TAG=17
15+
FROM postgres:${POSTGRES_TAG}
16+
17+
ARG CLOUDSYNC_VERSION
18+
ARG TARGETARCH
19+
20+
# Map Docker platform arch to artifact arch
21+
RUN case "${TARGETARCH}" in \
22+
amd64) ARCH="x86_64" ;; \
23+
arm64) ARCH="arm64" ;; \
24+
*) echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
25+
esac && \
26+
apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && \
27+
ASSET="cloudsync-postgresql${PG_MAJOR}-linux-${ARCH}-${CLOUDSYNC_VERSION}.tar.gz" && \
28+
URL="https://github.com/sqliteai/sqlite-sync/releases/download/${CLOUDSYNC_VERSION}/${ASSET}" && \
29+
echo "Downloading ${URL}" && \
30+
curl -fSL "${URL}" -o /tmp/cloudsync.tar.gz && \
31+
mkdir -p /tmp/cloudsync && \
32+
tar -xzf /tmp/cloudsync.tar.gz -C /tmp/cloudsync && \
33+
install -m 755 /tmp/cloudsync/cloudsync.so "$(pg_config --pkglibdir)/" && \
34+
install -m 644 /tmp/cloudsync/cloudsync--1.0.sql "$(pg_config --sharedir)/extension/" && \
35+
install -m 644 /tmp/cloudsync/cloudsync.control "$(pg_config --sharedir)/extension/" && \
36+
rm -rf /tmp/cloudsync /tmp/cloudsync.tar.gz && \
37+
apt-get purge -y curl && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*
38+
39+
# Verify installation
40+
RUN ls -la "$(pg_config --pkglibdir)/cloudsync.so" && \
41+
ls -la "$(pg_config --sharedir)/extension/cloudsync"* && \
42+
echo "CloudSync extension installed successfully"
43+
44+
# Copy initialization script (auto-creates the extension on first start)
45+
COPY docker/postgresql/init.sql /docker-entrypoint-initdb.d/
46+
47+
EXPOSE 5432
48+
49+
LABEL org.sqliteai.cloudsync.description="PostgreSQL with CloudSync CRDT extension" \
50+
org.opencontainers.image.source="https://github.com/sqliteai/sqlite-sync"
Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
# Supabase PostgreSQL with pre-compiled CloudSync (sqlite-sync) extension
2+
#
3+
# Usage:
4+
# docker build \
5+
# --build-arg SUPABASE_POSTGRES_TAG=15.8.1.085 \
6+
# --build-arg CLOUDSYNC_VERSION=1.0.1 \
7+
# -f docker/postgresql/Dockerfile.supabase.release \
8+
# -t my-cloudsync-supabase-postgres .
9+
#
10+
# Or pull the pre-built image from Docker Hub:
11+
# docker pull sqlitecloud/sqlite-sync-supabase:15
12+
#
13+
14+
ARG SUPABASE_POSTGRES_TAG=17.6.1.071
15+
FROM public.ecr.aws/supabase/postgres:${SUPABASE_POSTGRES_TAG}
16+
17+
ARG CLOUDSYNC_VERSION
18+
ARG TARGETARCH
19+
20+
ENV CLOUDSYNC_PG_CONFIG=/root/.nix-profile/bin/pg_config
21+
22+
# Download pre-compiled extension and install into Supabase's Nix layout
23+
RUN case "${TARGETARCH}" in \
24+
amd64) ARCH="x86_64" ;; \
25+
arm64) ARCH="arm64" ;; \
26+
*) echo "Unsupported architecture: ${TARGETARCH}" && exit 1 ;; \
27+
esac && \
28+
# Derive PG major version from pg_config
29+
PG_MAJOR=$(${CLOUDSYNC_PG_CONFIG} --version | sed 's/[^0-9]*//' | cut -d. -f1) && \
30+
apt-get update && apt-get install -y --no-install-recommends curl ca-certificates && \
31+
ASSET="cloudsync-postgresql${PG_MAJOR}-linux-${ARCH}-${CLOUDSYNC_VERSION}.tar.gz" && \
32+
URL="https://github.com/sqliteai/sqlite-sync/releases/download/${CLOUDSYNC_VERSION}/${ASSET}" && \
33+
echo "Downloading ${URL}" && \
34+
curl -fSL "${URL}" -o /tmp/cloudsync.tar.gz && \
35+
mkdir -p /tmp/cloudsync && \
36+
tar -xzf /tmp/cloudsync.tar.gz -C /tmp/cloudsync && \
37+
# Resolve Supabase's Nix library path
38+
PKGLIBDIR="$(${CLOUDSYNC_PG_CONFIG} --pkglibdir)" && \
39+
NIX_PGLIBDIR="$(grep -E '^export NIX_PGLIBDIR' /usr/bin/postgres | sed -E "s/.*'([^']+)'.*/\1/" || true)" && \
40+
if [ -n "$NIX_PGLIBDIR" ]; then PKGLIBDIR="$NIX_PGLIBDIR"; fi && \
41+
SHAREDIR_PGCONFIG="$(${CLOUDSYNC_PG_CONFIG} --sharedir)" && \
42+
SHAREDIR_STD="/usr/share/postgresql" && \
43+
install -d "$PKGLIBDIR" "$SHAREDIR_PGCONFIG/extension" && \
44+
install -m 755 /tmp/cloudsync/cloudsync.so "$PKGLIBDIR/" && \
45+
install -m 644 /tmp/cloudsync/cloudsync--1.0.sql /tmp/cloudsync/cloudsync.control "$SHAREDIR_PGCONFIG/extension/" && \
46+
if [ "$SHAREDIR_STD" != "$SHAREDIR_PGCONFIG" ]; then \
47+
install -d "$SHAREDIR_STD/extension" && \
48+
install -m 644 /tmp/cloudsync/cloudsync--1.0.sql /tmp/cloudsync/cloudsync.control "$SHAREDIR_STD/extension/"; \
49+
fi && \
50+
rm -rf /tmp/cloudsync /tmp/cloudsync.tar.gz && \
51+
apt-get purge -y curl && apt-get autoremove -y && rm -rf /var/lib/apt/lists/*
52+
53+
# Verify installation
54+
RUN NIX_PGLIBDIR="$(grep -E '^export NIX_PGLIBDIR' /usr/bin/postgres | sed -E "s/.*'([^']+)'.*/\1/" || true)" && \
55+
echo "Verifying CloudSync extension installation..." && \
56+
if [ -n "$NIX_PGLIBDIR" ]; then \
57+
ls -la "$NIX_PGLIBDIR/cloudsync.so"; \
58+
else \
59+
ls -la "$(${CLOUDSYNC_PG_CONFIG} --pkglibdir)/cloudsync.so"; \
60+
fi && \
61+
ls -la "$(${CLOUDSYNC_PG_CONFIG} --sharedir)/extension/cloudsync"* && \
62+
if [ -d "/usr/share/postgresql/extension" ]; then \
63+
ls -la /usr/share/postgresql/extension/cloudsync*; \
64+
fi && \
65+
echo "CloudSync extension installed successfully"
66+
67+
EXPOSE 5432
68+
69+
WORKDIR /
70+
71+
LABEL org.sqliteai.cloudsync.description="Supabase PostgreSQL with CloudSync CRDT extension" \
72+
org.opencontainers.image.source="https://github.com/sqliteai/sqlite-sync"

docs/TODO.md

Lines changed: 32 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -2,46 +2,48 @@
22

33
## CI / Releases
44

5-
- [ ] **Compile PostgreSQL extension against both PG15 and PG17**
6-
- The `postgres-build` CI job currently hardcodes PG17 (`postgresql-server-dev-17`)
7-
- Add a matrix over `['15', '17']` matching the existing `postgres-test` job
8-
- Name artifacts with the PG version: `cloudsync-postgresql-linux-x86_64-pg17-1.0.0.tar.gz`
9-
- Files compiled against one major PG version will not load on another
10-
- **This is a prerequisite for the standard Dockerfile improvement below**
5+
- [x] **Compile PostgreSQL extension against both PG15 and PG17**
6+
- The `postgres-build` CI job now builds for both PG 15 and PG 17 via matrix
7+
- Artifacts are named with the PG version: `cloudsync-postgresql17-linux-x86_64`, `cloudsync-postgresql15-linux-arm64`, etc.
118

129
## With Docker
1310

14-
Decision: do not publish to a registry. Instead provide Dockerfiles users build themselves.
11+
### Standard PostgreSQL (`Dockerfile.release`)
1512

16-
### Standard PostgreSQL (`Dockerfile`)
17-
18-
- [ ] **Update `Dockerfile` to download pre-compiled extension from GitHub releases instead of building from source**
19-
- Currently copies the full source tree and compiles inside the container
20-
- New approach: accept `--build-arg CLOUDSYNC_VERSION=1.0.0`, fetch the matching tarball from GitHub releases at build time
21-
- Dockerfile detects PG major version and architecture automatically to pick the right tarball
13+
- [x] **`Dockerfile.release` downloads pre-compiled extension from GitHub releases**
14+
- Accepts `--build-arg POSTGRES_TAG=17` and `--build-arg CLOUDSYNC_VERSION=1.0.0`
15+
- Detects PG major version (`PG_MAJOR`) and architecture (`TARGETARCH`) automatically to pick the right tarball
2216
- User only needs the Dockerfile — no full repo clone required
23-
- Example: `docker build --build-arg POSTGRES_TAG=17 --build-arg CLOUDSYNC_VERSION=1.0.0 -t my-cloudsync-postgres .`
24-
- **Blocked by:** release artifacts having PG version in the filename (see CI/Releases above)
25-
- **User flow (fresh):** Build image → use in `docker-compose.yml``CREATE EXTENSION cloudsync`
26-
- **User flow (existing):** Build image → swap in `docker-compose.yml` → restart → `CREATE EXTENSION cloudsync`
27-
28-
### Supabase (`Dockerfile.supabase`)
29-
30-
- Supabase uses a non-standard Nix-based PostgreSQL build — pre-compiled `.so` from releases will not work, must always compile from source
31-
- [ ] **Update `Dockerfile.supabase` to download source tarball from GitHub releases instead of using `COPY src/`**
32-
- Currently uses `COPY src/`, `COPY modules/`, `COPY Makefile` — requires the user to run `docker build` from inside the cloned repo
33-
- New approach: accept `--build-arg CLOUDSYNC_VERSION=1.0.0`, fetch the source tarball from GitHub releases at build time and compile inside the container
34-
- GitHub automatically generates source tarballs for every release (`Source code (tar.gz)`)
35-
- User only needs the `Dockerfile.supabase` — no repo clone required
36-
- Example: `docker build -f Dockerfile.supabase --build-arg SUPABASE_POSTGRES_TAG=15.8.1.085 --build-arg CLOUDSYNC_VERSION=1.0.0 -t my-cloudsync-supabase-postgres .`
37-
- **User flow (fresh):** Build image → replace default Supabase postgres image in `docker-compose.yml``CREATE EXTENSION cloudsync`
38-
- **User flow (existing):** Build image → follow Supabase's update guide to swap the postgres image → `CREATE EXTENSION cloudsync`
17+
- Example: `docker build --build-arg POSTGRES_TAG=17 --build-arg CLOUDSYNC_VERSION=1.0.0 -f docker/postgresql/Dockerfile.release -t my-cloudsync-postgres .`
18+
19+
- [x] **Pre-built Docker images published to Docker Hub on release**
20+
- `sqlitecloud/sqlite-sync-postgres:17`, `:15`, `:17-<version>`, `:15-<version>`
21+
- Multi-arch: `linux/amd64` + `linux/arm64`
22+
- Users can just `docker pull` — no build needed
23+
24+
### Supabase
25+
26+
Two Dockerfiles:
27+
- `Dockerfile.supabase` — builds from source (for development and testing, requires repo clone)
28+
- `Dockerfile.supabase.release` — downloads pre-built extension from GitHub releases (for end-users, no repo clone needed)
29+
30+
- [x] **Pre-built Supabase Docker images published to Docker Hub on release**
31+
- `sqlitecloud/sqlite-sync-supabase:17`, `:15`, `:17-<version>`, `:15-<version>`
32+
- Also tagged with the exact Supabase base image version (`:17.6.1.071`, `:15.8.1.085`)
33+
- Multi-arch: `linux/amd64` + `linux/arm64`
34+
- Users can reference the image directly in their Supabase `docker-compose.yml`
35+
36+
- [x] **`Dockerfile.supabase.release` downloads pre-compiled extension from GitHub releases**
37+
- Pre-built binaries are ABI-compatible with Supabase's Nix-based PostgreSQL (same PG major version)
38+
- Handles Supabase's non-standard Nix paths (`NIX_PGLIBDIR`, dual share directories)
39+
- Accepts `--build-arg SUPABASE_POSTGRES_TAG=15.8.1.085` and `--build-arg CLOUDSYNC_VERSION=1.0.0`
40+
- Example: `docker build -f docker/postgresql/Dockerfile.supabase.release --build-arg SUPABASE_POSTGRES_TAG=15.8.1.085 --build-arg CLOUDSYNC_VERSION=1.0.0 -t my-cloudsync-supabase .`
3941

4042
## Without Docker
4143

4244
For users with an existing native PostgreSQL installation (bare metal or VM, not containerized).
4345

44-
- [ ] **Ensure release tarballs are usable for native installs**
46+
- [x] **Release tarballs are usable for native installs**
4547
- User downloads the right tarball from GitHub releases (correct OS + arch + PG version)
4648
- Extracts 3 files: `cloudsync.so`, `cloudsync--1.0.sql`, `cloudsync.control`
4749
- Copies them to the PostgreSQL extension directories:
@@ -50,4 +52,3 @@ For users with an existing native PostgreSQL installation (bare metal or VM, not
5052
cp cloudsync--1.0.sql cloudsync.control $(pg_config --sharedir)/extension/
5153
```
5254
- Runs `CREATE EXTENSION cloudsync;` — done, no Docker or compiler needed
53-
- **Blocked by:** release artifacts having PG version in the filename (see CI/Releases above)

0 commit comments

Comments
 (0)