feat(zddc-server): publishable runtime image + Codeberg CI pipeline
Batch 1 of the chart-vs-project split. The project now ships a
hardened runtime image as part of every zddc-server release; downstream
deployments (e.g. the Burns & McDonnell Helm chart) will FROM this
image instead of cloning and building from source.
zddc/Containerfile (target: server)
- Tag the runtime stage `server` so `podman build --target server`
is unambiguous (the existing `binaries` target still works).
- Bake the bundled landing + archive tool HTML at /opt/zddc-server/web.
Useful for self-contained demos (`ZDDC_ROOT=/opt/zddc-server/web`)
and as a fallback web root when no external mount is supplied.
- Set fixed UID/GID 1000 for the non-root zddc user so volume
permissions are predictable across hosts.
- Add ENV ZDDC_ROOT=/srv default so a `podman run -v data:/srv` works
with no further config; explicit ZDDC_ROOT overrides.
- Declare VOLUME /srv to make the data-mount expectation explicit.
- Add OCI image labels (title, description, source, documentation,
license, vendor).
- Install ca-certificates so any future outbound HTTPS works.
- Add a HEALTHCHECK for `docker run` users (Kubernetes overrides).
build.sh
- Make the cross-platform podman binary build conditional on `podman`
being present. CI doesn't need it (the runtime container image's
own builder stage produces linux/amd64 internally), but having
build.sh sh-only-runnable means CI doesn't have to do nested
containers just to assemble dist/web.
- Reorder so `zddc/dist/web/` is assembled before the binary build
(allows the binary build to be skipped without breaking the bundle).
.woodpecker.yml (new)
- Triggers on tag push matching `zddc-server-v*`.
- Step 1 (alpine + sh): runs `sh build.sh` to assemble dist/web,
computes the image tag (`${TAG#zddc-server-v}` plus `latest`).
- Step 2 (docker-buildx plugin): builds and publishes
codeberg.org/varasys/zddc-server:{X.Y.Z, latest}. Auth via the
codeberg_user / codeberg_token Woodpecker secrets — these need
one-time setup in repo Settings; documented in zddc/README.md.
zddc/README.md
- New "Container image" section: pull URL, image properties (alpine,
non-root UID 1000, EXPOSE 8443, VOLUME /srv, baked web bundle),
example `podman run` invocation.
- New "Env-var contract (for chart consumers)" table: the variables
Helm charts and Compose files should set explicitly when running
behind a TLS-terminating reverse proxy with SSO. This is the
documented interface between project and downstream charts.
- "Release Tagging" section now points at .woodpecker.yml and lists
the two Woodpecker secrets that must be configured.
Validated locally:
podman build --target server -t zddc-server-test .
podman run -e ZDDC_ROOT=/opt/zddc-server/web -e ZDDC_TLS_CERT=none \
-e ZDDC_INSECURE_DIRECT=1 -e ZDDC_ADDR=:8080 \
-p 18080:8080 zddc-server-test
curl http://localhost:18080/ → HTTP 200, bundled landing tool.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
d122804bdb
commit
cc35f7179b
4 changed files with 186 additions and 12 deletions
54
.woodpecker.yml
Normal file
54
.woodpecker.yml
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
# Woodpecker CI for ZDDC.
|
||||
#
|
||||
# This pipeline only runs on `zddc-server-v*` tag pushes — it builds the
|
||||
# zddc-server runtime container image and publishes it to Codeberg's
|
||||
# container registry. Other tags (archive-v*, transmittal-v*, etc.) and
|
||||
# regular pushes are ignored here; the HTML tool releases happen by
|
||||
# pushing static files to the website (no image involved).
|
||||
#
|
||||
# To enable: in Codeberg → repo Settings → Woodpecker → set the secrets
|
||||
# codeberg_user = your Codeberg username (e.g. VARASYS)
|
||||
# codeberg_token = a personal token with package:write scope
|
||||
# Generate the token at https://codeberg.org/user/settings/applications.
|
||||
#
|
||||
# After setup, cut a release with:
|
||||
# git tag zddc-server-v0.0.1
|
||||
# git push --tags
|
||||
# and the pipeline will publish:
|
||||
# codeberg.org/varasys/zddc-server:0.0.1
|
||||
# codeberg.org/varasys/zddc-server:latest
|
||||
|
||||
when:
|
||||
- event: tag
|
||||
ref: refs/tags/zddc-server-v*
|
||||
|
||||
steps:
|
||||
prepare-bundle:
|
||||
image: docker.io/alpine:3.20
|
||||
commands:
|
||||
# build.sh assembles zddc/dist/web/ from landing and archive
|
||||
# built outputs (which are committed force-tracked dist files).
|
||||
# Falls back gracefully when podman isn't present — we don't
|
||||
# need the cross-compiled binaries here, the runtime container
|
||||
# builds its own linux/amd64 binary internally.
|
||||
- sh build.sh
|
||||
# Image tag = the bare semver after the "zddc-server-v" prefix.
|
||||
# Plus a "latest" tag for convenience.
|
||||
- VERSION="${CI_COMMIT_TAG#zddc-server-v}"
|
||||
- printf '%s\nlatest\n' "$VERSION" > .image-tags
|
||||
- echo "Will tag image with: $(cat .image-tags | tr '\n' ' ')"
|
||||
|
||||
publish-image:
|
||||
image: woodpeckerci/plugin-docker-buildx
|
||||
settings:
|
||||
registry: codeberg.org
|
||||
repo: codeberg.org/varasys/zddc-server
|
||||
dockerfile: zddc/Containerfile
|
||||
context: zddc
|
||||
target: server
|
||||
tags_file: .image-tags
|
||||
auto_tag: false
|
||||
username:
|
||||
from_secret: codeberg_user
|
||||
password:
|
||||
from_secret: codeberg_token
|
||||
19
build.sh
19
build.sh
|
|
@ -14,11 +14,6 @@ sh "$SCRIPT_DIR/classifier/build.sh" "${1:-}" "${2:-}"
|
|||
sh "$SCRIPT_DIR/mdedit/build.sh" "${1:-}" "${2:-}"
|
||||
sh "$SCRIPT_DIR/landing/build.sh" "${1:-}" "${2:-}"
|
||||
|
||||
echo ""
|
||||
echo "=== Building zddc-server binaries ==="
|
||||
mkdir -p "$SCRIPT_DIR/zddc/dist/web"
|
||||
podman build --target binaries -o "$SCRIPT_DIR/zddc/dist/" "$SCRIPT_DIR/zddc/" 2>&1 | grep -v "^-->"
|
||||
|
||||
echo ""
|
||||
echo "=== Assembling zddc/dist/web/ ==="
|
||||
# Only landing and archive ship inside the server bundle: they call the
|
||||
|
|
@ -26,11 +21,25 @@ echo "=== Assembling zddc/dist/web/ ==="
|
|||
# archive) and are useless without it. transmittal, classifier, and mdedit
|
||||
# are pure client-side tools that work from file:// or any static host;
|
||||
# they are released to website/ for download but not bundled with the server.
|
||||
mkdir -p "$SCRIPT_DIR/zddc/dist/web"
|
||||
cp "$SCRIPT_DIR/landing/dist/index.html" "$SCRIPT_DIR/zddc/dist/web/index.html"
|
||||
cp "$SCRIPT_DIR/archive/dist/archive.html" "$SCRIPT_DIR/zddc/dist/web/archive.html"
|
||||
echo "Wrote zddc/dist/web/index.html"
|
||||
echo "Wrote zddc/dist/web/archive.html"
|
||||
|
||||
# Cross-compiled zddc-server binaries are built via podman if available
|
||||
# (no-op otherwise — CI builds the runtime container directly via the
|
||||
# Containerfile's builder stage and doesn't need host-side binaries).
|
||||
echo ""
|
||||
echo "=== Building zddc-server binaries ==="
|
||||
if command -v podman >/dev/null 2>&1; then
|
||||
podman build --target binaries -o "$SCRIPT_DIR/zddc/dist/" "$SCRIPT_DIR/zddc/" 2>&1 | grep -v "^-->"
|
||||
else
|
||||
echo "podman not found — skipping cross-compiled binary build."
|
||||
echo " (CI builds the container image directly; this step only matters"
|
||||
echo " for releasing the standalone Linux/macOS/Windows binaries.)"
|
||||
fi
|
||||
|
||||
# ─── Bootstrap zips ──────────────────────────────────────────────────────────
|
||||
# Generated from bootstrap/level{1,2}.html.tmpl on every build so they are
|
||||
# always in sync with the current bootstrap pattern.
|
||||
|
|
|
|||
|
|
@ -1,4 +1,20 @@
|
|||
# syntax=docker/dockerfile:1
|
||||
#
|
||||
# Multi-stage build with three useful targets:
|
||||
#
|
||||
# --target binaries — scratch image holding cross-compiled binaries.
|
||||
# Use `podman build --target binaries -o dist/ .`
|
||||
# to extract zddc-server-{linux,darwin,windows}-*
|
||||
# to the host. No image published from this stage.
|
||||
#
|
||||
# --target server — alpine-based runtime. Default target. Published
|
||||
# as codeberg.org/varasys/zddc-server:vX.Y.Z.
|
||||
#
|
||||
# Build context expectations (when targeting `server`):
|
||||
# dist/web/index.html and dist/web/archive.html must exist —
|
||||
# produced by `sh build.sh` from the repo root. CI assembles these
|
||||
# before invoking podman. See .woodpecker.yml.
|
||||
#
|
||||
|
||||
# ─── Stage 1: build ──────────────────────────────────────────────────────────
|
||||
FROM docker.io/library/golang:1.24-alpine AS builder
|
||||
|
|
@ -35,16 +51,51 @@ RUN CGO_ENABLED=0 GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-s -w"
|
|||
FROM scratch AS binaries
|
||||
COPY --from=builder /out/ /
|
||||
|
||||
# ─── Stage 3: runtime ────────────────────────────────────────────────────────
|
||||
FROM docker.io/library/alpine:3.20
|
||||
# ─── Stage 3: runtime (published image) ─────────────────────────────────────
|
||||
FROM docker.io/library/alpine:3.20 AS server
|
||||
|
||||
# Non-root user
|
||||
RUN addgroup -S zddc && adduser -S -G zddc zddc
|
||||
LABEL org.opencontainers.image.title="zddc-server" \
|
||||
org.opencontainers.image.description="HTTP server for ZDDC archives — ACL via .zddc files, virtual archive index, audit logging" \
|
||||
org.opencontainers.image.source="https://codeberg.org/VARASYS/ZDDC" \
|
||||
org.opencontainers.image.documentation="https://zddc.varasys.io/zddc-server.html" \
|
||||
org.opencontainers.image.licenses="AGPL-3.0-only" \
|
||||
org.opencontainers.image.vendor="VARASYS"
|
||||
|
||||
# wget is in the base image (busybox); explicitly install ca-certificates
|
||||
# so outbound HTTPS (e.g. an upstream auth check) works if the operator
|
||||
# adds anything later. Keep the install footprint minimal.
|
||||
RUN apk add --no-cache ca-certificates && rm -rf /var/cache/apk/*
|
||||
|
||||
# Non-root user. UID/GID are deliberately fixed so volume permissions are
|
||||
# predictable across hosts.
|
||||
RUN addgroup -S -g 1000 zddc && adduser -S -u 1000 -G zddc zddc
|
||||
|
||||
# Binary
|
||||
COPY --from=builder /out/zddc-server-linux-amd64 /usr/local/bin/zddc-server
|
||||
|
||||
# Bundled landing + archive tools — useful for self-contained demos and as
|
||||
# a fallback web root. Set ZDDC_ROOT=/opt/zddc-server/web to serve only
|
||||
# these (no external data). For real archives, mount the data tree at
|
||||
# /srv (the default ZDDC_ROOT below).
|
||||
COPY dist/web/index.html /opt/zddc-server/web/index.html
|
||||
COPY dist/web/archive.html /opt/zddc-server/web/archive.html
|
||||
|
||||
# Conventional mount point for the served archive. Operators mount their
|
||||
# data here (Azure Files, NFS, hostPath, …). Override with ZDDC_ROOT.
|
||||
VOLUME /srv
|
||||
|
||||
USER zddc
|
||||
|
||||
# Default config: data mount at /srv. Override at run time as needed.
|
||||
# Other env vars (ZDDC_TLS_CERT, ZDDC_EMAIL_HEADER, ZDDC_CORS_ORIGIN, …)
|
||||
# are intentionally not defaulted — see zddc/README.md.
|
||||
ENV ZDDC_ROOT=/srv
|
||||
|
||||
EXPOSE 8443
|
||||
|
||||
# Liveness probe for `docker run` users. Kubernetes deployments override
|
||||
# this with their own livenessProbe / readinessProbe.
|
||||
HEALTHCHECK --interval=30s --timeout=5s --start-period=5s --retries=3 \
|
||||
CMD wget --no-check-certificate -q --spider https://localhost:8443/ || exit 1
|
||||
|
||||
ENTRYPOINT ["/usr/local/bin/zddc-server"]
|
||||
|
|
|
|||
|
|
@ -292,12 +292,59 @@ the actual built tool from `install.zip`, or a level-1/level-2 bootstrap stub th
|
|||
it. Then open it via the zddc-server URL; the app will auto-connect and scan the directory
|
||||
tree.
|
||||
|
||||
## Building
|
||||
## Container image
|
||||
|
||||
### Run as a container
|
||||
Each `zddc-server-vX.Y.Z` git tag publishes a runtime image to Codeberg's
|
||||
container registry via the Woodpecker CI pipeline at `.woodpecker.yml`:
|
||||
|
||||
```
|
||||
codeberg.org/varasys/zddc-server:X.Y.Z
|
||||
codeberg.org/varasys/zddc-server:latest
|
||||
```
|
||||
|
||||
Pull and run:
|
||||
|
||||
```sh
|
||||
podman build -t zddc-server .
|
||||
podman run --rm -p 8443:8443 \
|
||||
-v /srv/archive:/srv:Z \
|
||||
-e ZDDC_TLS_CERT=none \
|
||||
-e ZDDC_ADDR=:8080 \
|
||||
-e ZDDC_INSECURE_DIRECT=1 \
|
||||
codeberg.org/varasys/zddc-server:latest
|
||||
```
|
||||
|
||||
The image:
|
||||
|
||||
- alpine-based, runs as non-root (UID 1000)
|
||||
- exposes 8443 by default
|
||||
- defaults `ZDDC_ROOT=/srv` (override or mount your archive there)
|
||||
- bundles the landing + archive tool HTML at `/opt/zddc-server/web` for
|
||||
self-contained demos (`ZDDC_ROOT=/opt/zddc-server/web`)
|
||||
- declares `VOLUME /srv` so the operator's data mount is explicit
|
||||
- ships a `HEALTHCHECK` for `docker run`; Kubernetes deployments override it
|
||||
|
||||
### Env-var contract (for chart consumers)
|
||||
|
||||
Downstream Helm charts and Compose files should set these explicitly rather
|
||||
than relying on image defaults:
|
||||
|
||||
| Variable | Typical value (behind ingress + SSO) | Purpose |
|
||||
|---|---|---|
|
||||
| `ZDDC_ROOT` | `/srv` | Path of the served archive (volume mount) |
|
||||
| `ZDDC_TLS_CERT` | `none` | TLS terminated upstream |
|
||||
| `ZDDC_INSECURE_DIRECT` | `1` | Acknowledge plain HTTP behind a trusted proxy |
|
||||
| `ZDDC_ADDR` | `:8080` | Match service / probe port |
|
||||
| `ZDDC_EMAIL_HEADER` | `X-Auth-Request-Email` | Header your auth proxy sets |
|
||||
| `ZDDC_CORS_ORIGIN` | `https://your-host` | Origins permitted to call back into the server |
|
||||
|
||||
See "Environment Variables" above for the full list.
|
||||
|
||||
## Building
|
||||
|
||||
### Run as a container (build locally)
|
||||
|
||||
```sh
|
||||
podman build --target server -t zddc-server .
|
||||
```
|
||||
|
||||
### Compile native binaries (no Go installation required)
|
||||
|
|
@ -346,6 +393,19 @@ git tag zddc-server-v1.0.0
|
|||
git push --tags
|
||||
```
|
||||
|
||||
The `zddc-server-v*` tag triggers the `.woodpecker.yml` pipeline which
|
||||
builds and publishes the container image. See "Container image" above
|
||||
for the resulting image URLs.
|
||||
|
||||
The first time the pipeline runs you must configure two Woodpecker
|
||||
secrets in repo Settings → Woodpecker:
|
||||
|
||||
- `codeberg_user` — your Codeberg username (e.g. `VARASYS`)
|
||||
- `codeberg_token` — a personal token with `package:write` scope, generated at <https://codeberg.org/user/settings/applications>
|
||||
|
||||
These never live in the repo; they are referenced from the pipeline via
|
||||
`from_secret:`.
|
||||
|
||||
---
|
||||
|
||||
**Notes:**
|
||||
|
|
|
|||
Loading…
Reference in a new issue