Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
30 changes: 8 additions & 22 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -173,30 +173,16 @@ jobs:
- name: Build
run: go build -o duckgres .

- name: Start MinIO
- name: Start PGSTY Silo
run: |
# MinIO deleted its Docker Hub repositories; quay.io is the live source.
docker run -d --name minio \
-p 39000:9000 \
-e MINIO_ROOT_USER=minioadmin \
-e MINIO_ROOT_PASSWORD=minioadmin \
quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z server /data
# Wait for MinIO to be ready
for i in {1..30}; do
curl -sf http://localhost:39000/minio/health/live && break
sleep 1
done

- name: Create MinIO bucket
docker compose -f tests/integration/docker-compose.yml up -d --wait minio

- name: Create Silo bucket
run: |
# dl.min.io returns 410 Gone: MinIO archived the community mc client
# alongside the Docker Hub images. Run mc from its quay.io image
# instead. `--network container:minio` joins MinIO's own namespace,
# so the endpoint is its internal port, not the published one.
docker run --rm --network container:minio \
-e MC_HOST_minio=http://minioadmin:minioadmin@localhost:9000 \
quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z \
mb minio/ducklake --ignore-existing
docker compose -f tests/integration/docker-compose.yml run --rm --no-deps minio-init

- name: Verify Silo storage and credential revocation
run: just test-silo

- name: Run integration tests
run: just test-integration
Expand Down
10 changes: 8 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -733,7 +733,7 @@ larger steady-state metadata connection footprint.
The easiest way to get started with DuckLake is using the included Docker Compose setup:

```bash
# Start PostgreSQL (metadata) and MinIO (object storage)
# Start PostgreSQL (metadata) and PGSTY Silo (object storage)
docker compose up -d

# Wait for services to be ready
Expand All @@ -754,7 +754,7 @@ The `docker-compose.yaml` creates:
- Database: `ducklake`
- User/Password: `ducklake` / `ducklake`

**MinIO** (S3-compatible object storage):
**PGSTY Silo** (S3-compatible object storage):
- S3 API: `localhost:9000`
- Web Console: `http://localhost:9001`
- Access Key: `minioadmin`
Expand All @@ -763,6 +763,12 @@ The `docker-compose.yaml` creates:

The included `duckgres.yaml` is pre-configured to use both services.

Local and CI object storage use a pinned PGSTY Silo release with its bundled
`mcli` admin client. The Compose service names `minio` and `minio-init`,
`MINIO_*` settings, and existing endpoints are retained for compatibility.
See the [Silo local storage runbook](docs/silo-local-storage.md) for exact
image pins, migration, validation, and rollback instructions.

### Object Storage Configuration

DuckLake can store data files in S3-compatible object storage (AWS S3, MinIO, etc.). Two credential providers are supported:
Expand Down
14 changes: 6 additions & 8 deletions docker-compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -17,9 +17,7 @@ services:
retries: 5

minio:
# MinIO deleted its Docker Hub repositories, so pull from quay.io. The tag is a
# dated release rather than `latest` so a rebuild cannot silently change it.
image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
image: docker.io/pgsty/silo:RELEASE.2026-09-03T13-18-01Z@sha256:b616a0cf8cb281e7e6bb3c9b1fb53875b4016a2878223925541c18f82d6c5ca3
container_name: ducklake-storage
command: server /data --console-address ":9001"
environment:
Expand All @@ -31,23 +29,23 @@ services:
volumes:
- minio-data:/data
healthcheck:
test: ["CMD", "mc", "ready", "local"]
test: ["CMD", "mcli", "ready", "local"]
interval: 5s
timeout: 5s
retries: 5

# Creates the ducklake bucket on startup
minio-init:
image: quay.io/minio/mc:RELEASE.2025-08-13T08-35-41Z
image: docker.io/pgsty/silo:RELEASE.2026-09-03T13-18-01Z@sha256:b616a0cf8cb281e7e6bb3c9b1fb53875b4016a2878223925541c18f82d6c5ca3
container_name: ducklake-storage-init
depends_on:
minio:
condition: service_healthy
entrypoint: >
/bin/sh -c "
mc alias set minio http://minio:9000 minioadmin minioadmin;
mc mb minio/ducklake --ignore-existing;
mc anonymous set download minio/ducklake;
mcli alias set minio http://minio:9000 minioadmin minioadmin;
mcli mb minio/ducklake --ignore-existing;
mcli anonymous set download minio/ducklake;
echo 'Bucket ducklake created successfully';
"

Expand Down
76 changes: 76 additions & 0 deletions docs/silo-local-storage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Local and CI object storage with PGSTY Silo

Duckgres uses [PGSTY Silo](https://github.com/pgsty/silo), a maintained MinIO
fork, for local development and test object storage. Duckgres continues to use
the generic S3 API. This migration does not change production storage or the
real-S3 performance workflows.

## Release pins

Server and initialization containers use the same immutable multi-platform image:

```text
docker.io/pgsty/silo:RELEASE.2026-09-03T13-18-01Z@sha256:b616a0cf8cb281e7e6bb3c9b1fb53875b4016a2878223925541c18f82d6c5ca3
```

The image supports `linux/amd64` and `linux/arm64` and bundles `mcli` version
`RELEASE.2026-09-03T07-13-05Z`, pinning both server and admin client together.
The executables are `silo` and `mcli`. Existing `MINIO_*` variables, `/minio/*`
routes, Compose service/container/volume names, and S3 endpoints are retained.

## Defaults and local development

The root Compose stack still exposes S3 at `localhost:9000` and its console at
`http://localhost:9001`, with local credentials `minioadmin` / `minioadmin`.
It initializes the `ducklake` bucket with the existing anonymous-download
policy. Integration and local Kubernetes fixtures retain their existing port
mapping, buckets, credentials, reader policy, and metadata configuration; refer
to their Compose files for the separate fixture defaults.

Start and initialize root Compose storage:

```bash
docker compose up -d --wait minio
docker compose run --rm --no-deps minio-init
docker compose ps -a
docker compose logs minio minio-init
```

Duckgres application configuration and TLS setup are separate from this storage
setup. For integration storage, reconcile the pinned server and initialize it:

```bash
docker compose -f tests/integration/docker-compose.yml up -d --wait minio
docker compose -f tests/integration/docker-compose.yml run --rm --no-deps minio-init
just test-silo
just test-integration
```

`just test-silo` checks S3 access, reader policy, and credential revocation.
`SILO_TEST_CONTAINER` defaults to `duckgres-test-minio`; override it for an
isolated fixture using the same credentials. `just test-integration` alone
does not upgrade an already-running MinIO server. Trino checks use
`just trino-ducklake-smoke`, `just perf-trino-ducklake`, and
`just perf-trino-ducklake-realistic`. Kind and OrbStack dependencies use
`just run-multitenant-kind` and `just run-multitenant-local`.

## Migration and recovery

1. Record the previous checkout and actual server/client image digests; mutable
tags alone cannot reproduce the old stack. Stop writers, export any needed
data from tmpfs fixtures, then stop the affected stack.
2. Back up the full object volume (including hidden metadata) and PostgreSQL
data together while stopped. Do not use `down -v`. Integration and local
Kubernetes object-storage fixtures use tmpfs rather than persistent volumes.
3. Switch checkout, pull the pinned image, and run the setup commands above for
the intended stack, preserving its Compose project name and volumes.
4. Confirm healthy storage and successful initialization, then verify bucket
access and affected tests before resuming writers. On failure inspect
`docker compose ps -a` and `docker compose logs minio minio-init` (add the
integration Compose `-f` option for that stack); check ports and credentials.

To roll back, stop writers and the stack, preserve the failed state, and restore
the paired object/metadata backups, previous checkout, and recorded image
digests. Verify access before resuming work; do not assume an older MinIO image
can safely use data written by Silo. Disposable fixtures can instead be explicitly
discarded and reseeded, limited to that fixture's data.
13 changes: 9 additions & 4 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -152,15 +152,15 @@ check-multitenant-kind-ports:
[group('dev')]
multitenant-config-store-up: check-multitenant-local-ports
docker compose -f k8s/local-config-store.compose.yaml -f k8s/orbstack/dependency-ports.overlay.yaml up -d --wait
docker exec duckgres-local-minio mc alias set local http://127.0.0.1:9000 minioadmin minioadmin
docker exec duckgres-local-minio mc mb local/duckgres-local --ignore-existing
docker exec duckgres-local-minio mcli alias set local http://127.0.0.1:9000 minioadmin minioadmin
docker exec duckgres-local-minio mcli mb local/duckgres-local --ignore-existing

# Start the local PostgreSQL config store used by the kind-backed multi-tenant K8s flow
[group('dev')]
multitenant-config-store-up-kind: check-multitenant-kind-ports
docker compose -f k8s/local-config-store.compose.yaml -f k8s/kind/config-store.overlay.yaml up -d --wait
docker exec duckgres-local-minio mc alias set local http://127.0.0.1:9000 minioadmin minioadmin
docker exec duckgres-local-minio mc mb local/duckgres-local --ignore-existing
docker exec duckgres-local-minio mcli alias set local http://127.0.0.1:9000 minioadmin minioadmin
docker exec duckgres-local-minio mcli mb local/duckgres-local --ignore-existing

# Stop the local PostgreSQL config store used by the multi-tenant K8s flow
[group('dev')]
Expand Down Expand Up @@ -317,6 +317,11 @@ test-cache-proxy:
test-integration:
go test -v ./tests/integration/...

# Verify Silo S3 permissions and admin credential revocation after starting the integration stack.
[group('test')]
test-silo:
bash scripts/test_silo.sh

# Run shared/process control plane tests
[group('test')]
test-controlplane:
Expand Down
10 changes: 8 additions & 2 deletions k8s/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ The control plane handles TLS, authentication, PostgreSQL wire protocol, and SQL
| `kind/config-store.overlay.yaml` | Compose overlay that attaches local dependency containers to the external Docker `kind` network |
| `kind/config-store.seed.sql` | Kind-oriented managed-warehouse seed for the shared-worker flow |
| `kind/control-plane.yaml` | Kind-first shared-worker control-plane manifest used by local dev and CI |
| `orbstack/dependency-ports.overlay.yaml` | Optional OrbStack overlay that publishes local DuckLake and MinIO dependency ports on the host |
| `orbstack/dependency-ports.overlay.yaml` | Optional OrbStack overlay that publishes local DuckLake and PGSTY Silo dependency ports on the host |

## Configuration

Expand Down Expand Up @@ -104,7 +104,13 @@ PGPASSWORD=postgres psql "host=127.0.0.1 port=5432 user=postgres dbname=duckgres

`just multitenant-port-forward-pg` forwards pgwire on `5432`.

`just run-multitenant-kind` recreates a local kind cluster, starts the config store plus the local warehouse DB, DuckLake metadata DB, and MinIO backing the seeded managed-warehouse contract, attaches those dependency containers to the Docker `kind` network, loads the locally built image into kind, and deploys the shared-worker control plane.
`just run-multitenant-kind` recreates a local kind cluster, starts the config store plus the local warehouse DB, DuckLake metadata DB, and PGSTY Silo backing the seeded managed-warehouse contract, attaches those dependency containers to the Docker `kind` network, loads the locally built image into kind, and deploys the shared-worker control plane.

The local object store retains its `minio` service name, `MINIO_*` settings,
and existing dependency endpoints. Both kind and OrbStack use the pinned Silo
image and its bundled `mcli` admin client. See the
[Silo local storage runbook](../docs/silo-local-storage.md) before migrating
existing data or recovering a failed local setup.

Default login: `postgres / postgres`

Expand Down
6 changes: 2 additions & 4 deletions k8s/local-config-store.compose.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -47,16 +47,14 @@ services:
- /var/lib/postgresql/data

minio:
# MinIO deleted its Docker Hub repositories, so pull from quay.io. The tag is a
# dated release rather than `latest` so a rebuild cannot silently change it.
image: quay.io/minio/minio:RELEASE.2025-09-07T16-13-09Z
image: docker.io/pgsty/silo:RELEASE.2026-09-03T13-18-01Z@sha256:b616a0cf8cb281e7e6bb3c9b1fb53875b4016a2878223925541c18f82d6c5ca3
container_name: duckgres-local-minio
command: server /data --console-address ":9001"
environment:
MINIO_ROOT_USER: minioadmin
MINIO_ROOT_PASSWORD: minioadmin
healthcheck:
test: ["CMD", "mc", "ready", "local"]
test: ["CMD", "mcli", "ready", "local"]
interval: 5s
timeout: 5s
retries: 10
Expand Down
22 changes: 11 additions & 11 deletions scripts/seed_ducklake.sh
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
#!/bin/bash
# Seed script for DuckLake catalog with sample data stored in MinIO
# Seed script for DuckLake catalog with sample data stored in PGSTY Silo
#
# This script connects to a running Duckgres server with DuckLake configured
# (PostgreSQL metadata + MinIO object storage) and creates tables with sample
# data. DuckLake automatically stores the data as Parquet files in MinIO.
# (PostgreSQL metadata + Silo object storage) and creates tables with sample
# data. DuckLake automatically stores the data as Parquet files in Silo.
#
# Prerequisites:
# - docker compose up -d (PostgreSQL + MinIO running)
# - docker compose up -d (PostgreSQL + Silo running)
# - ./duckgres --config duckgres.yaml (Duckgres server running)
#
# Usage:
Expand Down Expand Up @@ -54,7 +54,7 @@ while [[ $# -gt 0 ]]; do
-h|--help)
echo "Usage: $0 [options]"
echo ""
echo "Seeds the DuckLake catalog with sample data stored as Parquet in MinIO."
echo "Seeds the DuckLake catalog with sample data stored as Parquet in Silo."
echo ""
echo "Options:"
echo " --host HOST Server host (default: 127.0.0.1)"
Expand All @@ -79,7 +79,7 @@ done
CONN="host=$HOST port=$PORT user=$USER sslmode=require"

echo "=== DuckLake Catalog Seed Script ==="
echo "Seeding DuckLake with Parquet data in MinIO"
echo "Seeding DuckLake with Parquet data in Silo"
echo ""
echo "Connecting to $HOST:$PORT as $USER..."

Expand Down Expand Up @@ -113,7 +113,7 @@ if ! run_sql "SHOW ALL TABLES" 2>&1 | grep -q "ducklake"; then
echo ""
echo "Make sure duckgres.yaml has DuckLake configured with:"
echo " - metadata_store pointing to PostgreSQL"
echo " - object_store pointing to MinIO (s3://ducklake/data/)"
echo " - object_store pointing to Silo (s3://ducklake/data/)"
echo " - S3 credentials configured"
exit 1
fi
Expand All @@ -135,7 +135,7 @@ if [ "$CLEAN" = true ]; then
fi

echo "=== Creating Tables in DuckLake Catalog ==="
echo "(Data will be stored as Parquet files in MinIO)"
echo "(Data will be stored as Parquet files in Silo)"
echo ""

# Create categories table
Expand Down Expand Up @@ -240,7 +240,7 @@ EOF

echo ""
echo "=== Inserting Data into DuckLake ==="
echo "(This creates Parquet files in MinIO)"
echo "(This creates Parquet files in Silo)"
echo ""

# Seed categories
Expand Down Expand Up @@ -410,9 +410,9 @@ echo "=== Seed Complete! ==="
echo ""
echo "Data is now stored in DuckLake with:"
echo " - Metadata in PostgreSQL (localhost:5433)"
echo " - Parquet files in MinIO (localhost:9000, bucket: ducklake)"
echo " - Parquet files in Silo (localhost:9000, bucket: ducklake)"
echo ""
echo "View MinIO console at: http://localhost:9001"
echo "View Silo console at: http://localhost:9001"
echo " Username: minioadmin"
echo " Password: minioadmin"
echo ""
Expand Down
Loading
Loading