From f48a29fa7561ec37c9d80c76919b486df0f015b4 Mon Sep 17 00:00:00 2001 From: "Vitaly D." Date: Sun, 8 Mar 2026 20:19:14 +0300 Subject: [PATCH] Add siftd container image pipeline --- .dockerignore | 10 +++++ .github/workflows/publish-image.yml | 69 +++++++++++++++++++++++++++++ Dockerfile | 35 +++++++++++++++ README.md | 3 +- docs/README.md | 2 +- docs/runbooks/siftd.md | 15 +++++++ llms.txt | 2 + 7 files changed, 134 insertions(+), 2 deletions(-) create mode 100644 .dockerignore create mode 100644 .github/workflows/publish-image.yml create mode 100644 Dockerfile diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..405930a --- /dev/null +++ b/.dockerignore @@ -0,0 +1,10 @@ +.git +.github +.codex +.claude +output +state +tmp +*.db +*.sqlite +*.sqlite3 diff --git a/.github/workflows/publish-image.yml b/.github/workflows/publish-image.yml new file mode 100644 index 0000000..78aa54b --- /dev/null +++ b/.github/workflows/publish-image.yml @@ -0,0 +1,69 @@ +name: Publish Image + +on: + push: + branches: + - main + paths: + - .dockerignore + - Dockerfile + - .github/workflows/publish-image.yml + - cmd/siftd/** + - internal/** + - docs/contracts/source-registry.seed.json + - go.mod + - go.sum + +jobs: + build-push: + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - name: Checkout + uses: actions/checkout@v4 + + - name: Setup Go + uses: actions/setup-go@v5 + with: + go-version-file: go.mod + cache: true + + - name: Run Unit Tests + run: go test ./... + + - name: Run Clustering Eval Gate + run: go run ./cmd/sift eval clustering --dataset docs/contracts/clustering-eval.v0.json --threshold 0.82 --target-precision 0.90 --min-pairs 100 --format text + + - name: Setup Buildx + uses: docker/setup-buildx-action@v3 + + - name: Login to GHCR + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Generate image tag + id: tag + env: + GIT_SHA: ${{ github.sha }} + run: | + SHA7=$(echo "$GIT_SHA" | cut -c1-7) + TS14=$(date -u +%Y%m%d%H%M%S) + echo "tag=dev-${SHA7}-${TS14}" >> "$GITHUB_OUTPUT" + + - name: Build and push image + uses: docker/build-push-action@v6 + with: + context: . + file: Dockerfile + push: true + tags: | + ghcr.io/heurema/sift:${{ steps.tag.outputs.tag }} + ghcr.io/heurema/sift:latest + cache-from: type=gha + cache-to: type=gha,mode=max diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..ecf2448 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,35 @@ +FROM golang:1.25.5-alpine AS builder +WORKDIR /src + +RUN apk add --no-cache ca-certificates tzdata + +COPY go.mod go.sum ./ +RUN go mod download + +COPY cmd ./cmd +COPY internal ./internal +COPY docs/contracts/source-registry.seed.json ./docs/contracts/source-registry.seed.json + +RUN CGO_ENABLED=0 GOOS=linux go build -trimpath -ldflags="-s -w" -o /out/siftd ./cmd/siftd + +FROM alpine:3.21 + +RUN apk add --no-cache ca-certificates tzdata \ + && addgroup -S app \ + && adduser -S app -G app \ + && install -d -o app -g app /app /app/docs/contracts /data/output + +WORKDIR /app + +COPY --from=builder /out/siftd /app/siftd +COPY --from=builder /src/docs/contracts/source-registry.seed.json /app/docs/contracts/source-registry.seed.json + +ENV SIFTD_OUTPUT_DIR=/data/output + +USER app + +EXPOSE 8080 + +HEALTHCHECK --interval=30s --timeout=3s --start-period=20s --retries=3 CMD wget -qO- http://127.0.0.1:8080/healthz >/dev/null || exit 1 + +ENTRYPOINT ["/app/siftd"] diff --git a/README.md b/README.md index d1539c5..8095156 100644 --- a/README.md +++ b/README.md @@ -90,7 +90,8 @@ Implemented now: - health and readiness endpoints (`/healthz`, `/readyz`); - Zitadel-protected read API (`/v1/events`, `/v1/events/{event_id}`, `/v1/digests/{scope}/{window}`); - authenticated WebSocket stream endpoint (`/v1/ws`) with post-sync update notifications (`event.upserted`, `digest.updated`). - - operator deployment assets for single-node Linux hosting (`ops/systemd/siftd.service`, `ops/env/siftd.env.example`, `docs/runbooks/siftd.md`). + - operator deployment assets for single-node Linux hosting (`ops/systemd/siftd.service`, `ops/env/siftd.env.example`, `docs/runbooks/siftd.md`); + - container build artifact (`Dockerfile`) and GHCR publish path (`ghcr.io/heurema/sift`) with deterministic `dev--` tags from CI. Not implemented yet: diff --git a/docs/README.md b/docs/README.md index 7a38e81..30411a0 100644 --- a/docs/README.md +++ b/docs/README.md @@ -21,7 +21,7 @@ ## Runbooks -- [siftd.md](runbooks/siftd.md) — single-node operator runbook for the hosted `siftd` service +- [siftd.md](runbooks/siftd.md) — operator runbook for the hosted `siftd` service, including single-node host deployment and container artifact notes ## Research diff --git a/docs/runbooks/siftd.md b/docs/runbooks/siftd.md index b2d9595..9e04090 100644 --- a/docs/runbooks/siftd.md +++ b/docs/runbooks/siftd.md @@ -13,12 +13,27 @@ Target model: - one reverse proxy in front of `127.0.0.1:8080` if public HTTPS is required. This is intentionally a single-node operating model. +The repository also publishes a container image for GitOps and cluster deployment, but +this runbook remains the host-first operating baseline. ## Repository Assets - systemd unit: `ops/systemd/siftd.service` - environment template: `ops/env/siftd.env.example` - binary entrypoint: `cmd/siftd/main.go` +- container image build: `Dockerfile` +- CI image publish workflow: `.github/workflows/publish-image.yml` + +## Container Artifact + +The repository publishes: + +- image: `ghcr.io/heurema/sift` +- mutable convenience tag: `latest` +- rollout tag format: `dev--` + +The hosted GitOps path should pin one of the `dev-*` tags. `latest` is for manual +inspection and ad-hoc runs only. ## Required Inputs diff --git a/llms.txt b/llms.txt index 5e11395..1417753 100644 --- a/llms.txt +++ b/llms.txt @@ -16,6 +16,8 @@ Key themes: crypto news, event clustering, provenance, rights-aware ingestion, a - [Sift Pro Execution Plan](docs/plans/2026-03-07-sift-pro-execution-plan.md): Narrow hosted implementation path for the first paid service slice, now anchored on Postgres and Zitadel. - [Sift Pro Slice 1 Blueprint](docs/plans/2026-03-07-sift-pro-slice-1-blueprint.md): File-level extraction plan for the shared runtime that keeps local SQLite separate from the future hosted Postgres path. - [Siftd Runbook](docs/runbooks/siftd.md): Single-node operator runbook for deploying the hosted service with systemd, Postgres, and Zitadel. +- [Dockerfile](Dockerfile): Multi-stage container image definition for the hosted `siftd` runtime. +- [Publish Image Workflow](.github/workflows/publish-image.yml): GHCR image publication for `ghcr.io/heurema/sift` on `main` when hosted runtime inputs change. - [CLI Entry](cmd/sift/main.go): Current Go CLI implementation entry point. - [Server Entry](cmd/siftd/main.go): Hosted server entry point with Postgres-backed scheduler and Zitadel-protected read path. - [Siftd Env Example](ops/env/siftd.env.example): Example hosted environment contract for systemd deployment.