Skip to content
Merged
Show file tree
Hide file tree
Changes from 52 commits
Commits
Show all changes
63 commits
Select commit Hold shift + click to select a range
6f019d7
feat(cli): check generated service containers
iamgp Jul 22, 2026
6f33dda
fix(cli): report all generated container failures
iamgp Jul 22, 2026
d0bcbde
fix(cli): run container checks through docker
iamgp Jul 22, 2026
a101f1c
fix(cli): invoke hadolint image correctly
iamgp Jul 22, 2026
0ded6a3
fix(containers): harden generated service images
iamgp Jul 22, 2026
b21fe2b
ci: run generated container checks
iamgp Jul 22, 2026
82b9e9a
fix(containers): scan all generated service images
iamgp Jul 23, 2026
494c83e
test(dagster): update runtime install contract
iamgp Jul 23, 2026
864878a
fix(containers): bound scanner failure output
iamgp Jul 24, 2026
156a09c
fix(containers): refresh generated service images
iamgp Jul 24, 2026
227ca22
perf(containers): reuse trivy scan cache
iamgp Jul 24, 2026
4fef812
fix(containers): remove vulnerabilities from owned images
iamgp Jul 24, 2026
d98fd89
fix(containers): harden generated pgweb image
iamgp Jul 24, 2026
d455b3e
fix(containers): pin observatory apk package
iamgp Jul 24, 2026
48b257a
feat(containers): generate hardened image wrappers
iamgp Jul 24, 2026
f1f29b4
fix(containers): build wrappers as root
iamgp Jul 25, 2026
ac2e4d0
fix(containers): make vulnerability waivers explicit
iamgp Jul 26, 2026
c7f4743
fix(containers): harden pinned service images
iamgp Jul 26, 2026
695ddae
fix(containers): harden auth and exporter images
iamgp Jul 26, 2026
8dd23be
fix(registry): sync pinned service images
iamgp Jul 26, 2026
4ced978
fix(cli): preserve container vulnerability evidence
iamgp Jul 26, 2026
0392ed8
fix(clickhouse): pin the stable Alpine image
iamgp Jul 26, 2026
f14a956
fix(containers): set explicit runtime users
iamgp Jul 26, 2026
b958d2d
fix(loki): rebuild stable release with patched grpc
iamgp Jul 26, 2026
349761d
fix(minio): restore the runtime maintenance client
iamgp Jul 26, 2026
4c51ef5
fix(alloy): rebuild current release with patched grpc
iamgp Jul 26, 2026
89df33b
fix(openmetadata): patch current service images
iamgp Jul 26, 2026
44c6b25
fix(trino): rebuild launcher with patched Go
iamgp Jul 26, 2026
011484d
fix(cli): allow large container scans to complete
iamgp Jul 26, 2026
a3eb716
fix(nessie): rebuild stable image with patched libraries
iamgp Jul 26, 2026
30fc77d
fix(grafana): rebuild stable image with patched backends
iamgp Jul 26, 2026
2d64bf1
fix(openmetadata): patch elasticsearch libraries
iamgp Jul 26, 2026
1352a3e
fix(superset): harden the stable runtime image
iamgp Jul 26, 2026
0bec1f3
fix(clickstack): harden the stable all-in-one image
iamgp Jul 26, 2026
59e47be
fix(containers): narrow vulnerability waivers
iamgp Jul 26, 2026
f1a2d25
fix(cli): force disposable service generation
iamgp Jul 26, 2026
1f40aef
fix(cli): attribute companion container files
iamgp Jul 26, 2026
6fbd3c5
fix(alloy): declare the runtime user
iamgp Jul 26, 2026
9cf937e
fix(cli): retain large vulnerability reports
iamgp Jul 26, 2026
56381e9
fix(containers): use generated build contexts
iamgp Jul 26, 2026
7464c0d
fix(cli): build shared container images once
iamgp Jul 26, 2026
dbb1d01
fix(postgrest): minimize the stable runtime image
iamgp Jul 26, 2026
db48b80
fix(alloy): bound transient build storage
iamgp Jul 26, 2026
eec6157
fix(cli): bound generated container disk use
iamgp Jul 26, 2026
68d63ae
fix(cli): prune generated build cache incrementally
iamgp Jul 26, 2026
c7053cc
fix(cli): harden container scan waivers
iamgp Jul 26, 2026
b6ab8e4
fix(ci): harden generated container gate
iamgp Jul 26, 2026
2d5a8dd
fix(ci): hydrate container wheelhouse
iamgp Jul 26, 2026
b407150
fix(cli): restore pulled image tags
iamgp Jul 26, 2026
f9d819a
fix(ci): repair container gate metadata
iamgp Jul 26, 2026
32056b1
fix(qa): wait for WAP rejection evidence
iamgp Jul 26, 2026
1d7cb49
fix(containers): resolve upgrade safety reviews
iamgp Jul 26, 2026
6b4a91c
feat(containers): publish generated service images
iamgp Jul 26, 2026
bfe7ca7
fix(registry): record published service images
iamgp Jul 26, 2026
6ac61bb
fix(containers): target image publication retries
iamgp Jul 26, 2026
9c50858
chore(containers): link published images to repository
iamgp Jul 26, 2026
80f35cc
fix(containers): publish images on native runners
iamgp Jul 26, 2026
b3b2e3d
fix(containers): publish native image digests
iamgp Jul 26, 2026
80d54b5
fix(containers): bypass unreliable image proxies
iamgp Jul 27, 2026
8fe001e
fix(containers): isolate manifest digest artifacts
iamgp Jul 27, 2026
8883706
fix(ci): stabilize remote container scan
iamgp Jul 27, 2026
cccb15e
fix(ci): align pinned Trivy digest
iamgp Jul 27, 2026
eb85f7d
fix(cli): escape scanner output markup
iamgp Jul 27, 2026
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
106 changes: 106 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -295,6 +295,108 @@ jobs:
exit 1
fi

generated-container-checks:
name: containers / generated service files
runs-on: ubuntu-latest
timeout-minutes: 120
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd
with:
persist-credentials: false

- name: Install uv
uses: astral-sh/setup-uv@5a095e7a2014a4212f075830d4f7277575a9d098
with:
version: "0.10.10"

- name: Set up Python
run: uv python install 3.11

- name: Build Phlo and service wheels
run: uv build --all-packages --wheel --out-dir "$RUNNER_TEMP/phlo-container-wheelhouse"

- name: Hydrate the container wheelhouse
shell: bash
run: |
set -euo pipefail
wheelhouse="$RUNNER_TEMP/phlo-container-wheelhouse"
distributions=(
phlo phlo-alloy phlo-api phlo-clickhouse phlo-clickstack phlo-dagster
phlo-grafana phlo-hasura phlo-loki phlo-minio phlo-nessie phlo-oauth2-proxy
phlo-openmetadata phlo-observatory phlo-pgweb phlo-postgres phlo-postgrest
phlo-prometheus phlo-rustfs phlo-superset phlo-traefik phlo-trino
)
local_wheels=()
shopt -s nullglob
for distribution in "${distributions[@]}"; do
normalized="${distribution//-/_}"
matches=("$wheelhouse"/"$normalized"-*.whl)
if [ "${#matches[@]}" -ne 1 ]; then
echo "Expected one wheel for $distribution, found ${#matches[@]}" >&2
exit 1
fi
local_wheels+=("${matches[0]}")
done
uvx --from pip==26.1.2 pip download \
--dest "$wheelhouse" \
--find-links "$wheelhouse" \
"${local_wheels[@]}"

- name: Cache Trivy vulnerability and scan data
uses: actions/cache@0057852bfaa89a56745cba8c7296529d2fc39830 # v4
with:
path: ${{ runner.temp }}/phlo-trivy-cache
key: trivy-${{ runner.os }}-v2-${{ github.run_number }}
restore-keys: |
trivy-${{ runner.os }}-v2-

- name: Install wheels into a clean environment
shell: bash
run: |
set -euo pipefail
env_dir="$RUNNER_TEMP/phlo-container-env"
uv venv "$env_dir" --python 3.11
uv pip install \
--python "$env_dir/bin/python" \
--no-index \
--find-links "$RUNNER_TEMP/phlo-container-wheelhouse" \
phlo \
phlo-alloy \
phlo-api \
phlo-clickhouse \
phlo-clickstack \
phlo-dagster \
phlo-grafana \
phlo-hasura \
phlo-loki \
phlo-minio \
phlo-nessie \
phlo-oauth2-proxy \
phlo-openmetadata \
phlo-observatory \
phlo-pgweb \
phlo-postgres \
phlo-postgrest \
phlo-prometheus \
phlo-rustfs \
phlo-superset \
phlo-traefik \
phlo-trino
echo "$env_dir/bin" >> "$GITHUB_PATH"

- name: Check generated service containers
env:
PHLO_TRIVY_CACHE_DIR: ${{ runner.temp }}/phlo-trivy-cache
run: |
phlo --no-color plugin check --containers \
--allow-vulnerable-image "alloy=phlo/alloy:v1.18.0-go1.26.5=6a7628c7ba66eb8a0fb70569a36b90012bdb9e0e5b0fa4241331d62179b0528b=Alloy 1.18.0 retains two Docker module advisories without fixes and one whose fix requires an incompatible Docker client major" \
--allow-vulnerable-image "clickstack=phlo/clickstack:2.31.0-security-patches=279df6dbb8bd358983e0be65a5bfe46fa6ced3738a10a55798a2d59016721db9=Stable 2.31.0 still requires legacy MongoDB OpenSSL 1.1, root orchestration, rebuild-only Next changes, and unreleased OTel binary fixes" \
--allow-vulnerable-image "grafana=phlo/grafana:13.1.1-go1.26.5=776f3f74541a97642ef470c93c4e04aed1325f81547e268564c87368f8fce669=Trivy reports a stale Tempo pseudo-version although the image rebuilds exact Tempo 2.10.7 source containing both fixes" \
--allow-vulnerable-image "minio=phlo/minio:7aac2a2c5b7c=b8d9b4a71a6e9c05e3ba3931ada391b3dce6b46404d303ee373ec46acf994163=Archived public source still has six MinIO-module advisories without public patches; OS, standard library, and third-party Go findings are remediated" \
--allow-vulnerable-image "openmetadata-elasticsearch=phlo/openmetadata-elasticsearch:8.11.4-java-patches=d2d0eaf6881362fd497f981d3f9f5e4b431fd072044decda8acf3612ef2d3f3e=Elasticsearch 8.11.4 shades three old Jackson versions, requires Tika 2.x, and has one unpublished and one unfixed LZ4 advisory" \
--allow-vulnerable-image "superset=phlo/superset:6.1.0-security-patches=064bf36c4a8366c2077b576e6d08b703a7d0fd114110f0e9243b5090fdaf051e=Superset requires three vulnerable Python versions and Debian 12 has no fixed packages for the remaining operating system advisories" \
--allow-vulnerable-image "trino=phlo/trino:483-launcher318-go1.26.5=3f18aa318a5cdca2cffe51a1f6768d3e8c3bc6feee880f61ee0f53e0c7e8add6=Trino 483 requires Jetty 11 while the remaining HTTP advisory is fixed only in incompatible Jetty 12"

release-golden-path:
name: python / release golden path
runs-on: ubuntu-latest
Expand Down Expand Up @@ -496,6 +598,7 @@ jobs:
- python-quality
- python-core-tests
- quickstart-smoke
- generated-container-checks
- release-golden-path
- recovery-continuity-drill
- windows-release-contract
Expand All @@ -509,6 +612,7 @@ jobs:
PYTHON_QUALITY: ${{ needs.python-quality.result }}
PYTHON_CORE_TESTS: ${{ needs.python-core-tests.result }}
QUICKSTART_SMOKE: ${{ needs.quickstart-smoke.result }}
GENERATED_CONTAINER_CHECKS: ${{ needs.generated-container-checks.result }}
RELEASE_GOLDEN_PATH: ${{ needs.release-golden-path.result }}
RECOVERY_CONTINUITY_DRILL: ${{ needs.recovery-continuity-drill.result }}
WINDOWS_RELEASE_CONTRACT: ${{ needs.windows-release-contract.result }}
Expand All @@ -526,6 +630,7 @@ jobs:
echo "| python / quality | ${PYTHON_QUALITY} |"
echo "| python / core tests | ${PYTHON_CORE_TESTS} |"
echo "| python / quickstart smoke | ${QUICKSTART_SMOKE} |"
echo "| containers / generated service files | ${GENERATED_CONTAINER_CHECKS} |"
echo "| python / release golden path | ${RELEASE_GOLDEN_PATH} |"
echo "| python / recovery continuity drill | ${RECOVERY_CONTINUITY_DRILL} |"
echo "| windows / release golden path contract | ${WINDOWS_RELEASE_CONTRACT} |"
Expand All @@ -538,6 +643,7 @@ jobs:
"${PYTHON_QUALITY}" \
"${PYTHON_CORE_TESTS}" \
"${QUICKSTART_SMOKE}" \
"${GENERATED_CONTAINER_CHECKS}" \
"${RELEASE_GOLDEN_PATH}" \
"${RECOVERY_CONTINUITY_DRILL}" \
"${WINDOWS_RELEASE_CONTRACT}" \
Expand Down
1 change: 1 addition & 0 deletions packages/phlo-alloy/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ include-package-data = true

[tool.setuptools.package-data]
phlo_alloy = [
"Dockerfile",
"service.yaml",
"config.alloy",
]
Expand Down
26 changes: 26 additions & 0 deletions packages/phlo-alloy/src/phlo_alloy/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
FROM grafana/alloy-build-image:v0.1.34@sha256:4371598809230ffd7611367ba0dcb92bbc8b74bda88471293e5d478b48a0d186 AS build

RUN git init /src/alloy && \
git -C /src/alloy remote add origin https://github.com/grafana/alloy.git && \
git -C /src/alloy fetch --depth 1 origin a435563ff073d5355952c1a8d1821110b1392691 && \
git -C /src/alloy checkout --detach FETCH_HEAD && \
rm -rf /src/alloy/.git
WORKDIR /src/alloy
# hadolint ignore=DL3062
RUN export GOCACHE=/tmp/go-cache GOMODCACHE=/tmp/go-mod && \
go get google.golang.org/grpc@v1.82.1 && \
go mod tidy && \
npm --prefix internal/web/ui ci --no-audit --no-fund && \
npm --prefix internal/web/ui run build && \
RELEASE_BUILD=1 \
VERSION=v1.18.0 \
GO_TAGS="netgo embedalloyui promtail_journal_enabled" \
SKIP_UI_BUILD=1 \
make alloy && \
rm -rf internal/web/ui/node_modules /tmp/go-cache /tmp/go-mod

FROM grafana/alloy:v1.18.0@sha256:491b0578c04983fd54fe99b587b6fab4404dc46d0dc16677bd6b00cc1140b308

COPY --from=build /src/alloy/build/alloy /bin/alloy

USER "473"
7 changes: 6 additions & 1 deletion packages/phlo-alloy/src/phlo_alloy/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,10 @@ category: observability
default: false
profile: observability

image: grafana/alloy:v1.4.2
image: phlo/alloy:v1.18.0-go1.26.5
build:
context: .
dockerfile: alloy/Dockerfile

compose:
restart: unless-stopped
Expand Down Expand Up @@ -36,5 +39,7 @@ env_vars:
description: Alloy HTTP port

files:
- source: Dockerfile
dest: alloy/Dockerfile
- source: config.alloy
dest: alloy/config.alloy
27 changes: 27 additions & 0 deletions packages/phlo-alloy/tests/test_alloy_plugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
"""Tests for Alloy service plugin."""

from importlib import resources

from phlo_alloy.plugin import AlloyServicePlugin


Expand All @@ -12,6 +14,31 @@ def test_alloy_service_definition():
assert defn["profile"] == "observability"


def test_alloy_service_builds_patched_release_image() -> None:
"""Generated Alloy uses the stable release with fixed embedded Go dependencies."""
definition = AlloyServicePlugin().service_definition

assert definition["image"] == "phlo/alloy:v1.18.0-go1.26.5"
assert definition["build"] == {"context": ".", "dockerfile": "alloy/Dockerfile"}


def test_alloy_runtime_image_sets_the_upstream_non_root_user() -> None:
"""The generated Dockerfile keeps Alloy's upstream runtime identity explicit."""
dockerfile = resources.files("phlo_alloy").joinpath("Dockerfile").read_text()

assert dockerfile.rstrip().endswith('USER "473"')


def test_alloy_builder_discards_source_control_and_dependency_caches() -> None:
"""The generated build must not retain multi-gigabyte transient dependency trees."""
dockerfile = resources.files("phlo_alloy").joinpath("Dockerfile").read_text()

assert "git init /src/alloy" in dockerfile
assert "git -C /src/alloy fetch --depth 1 origin" in dockerfile
assert "rm -rf /src/alloy/.git" in dockerfile
assert "rm -rf internal/web/ui/node_modules /tmp/go-cache /tmp/go-mod" in dockerfile


def test_alloy_plugin_metadata():
"""Validate Alloy plugin metadata."""
plugin = AlloyServicePlugin()
Expand Down
25 changes: 21 additions & 4 deletions packages/phlo-api/src/phlo_api/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
FROM python:3.11-slim AS phlo-build-context
FROM python:3.11-alpine AS phlo-build-context

WORKDIR /opt/phlo-build-context

Expand All @@ -8,15 +8,23 @@ RUN set -eux; \
install -m 0755 "$entrypoint" /opt/phlo-build-context/phlo-api-entrypoint.sh; \
mkdir -p /opt/phlo-build-context/wheelhouse

FROM python:3.11-slim
FROM python:3.11-alpine

WORKDIR /app

ARG PHLO_VERSION=
ARG PHLO_API_VERSION=
ARG PHLO_WHEELHOUSE=

RUN pip install --no-cache-dir uv
RUN apk upgrade --no-cache \
&& apk add --no-cache ca-certificates=20260611-r0 \
&& apk add --no-cache --virtual .build-deps \
gcc=15.2.0-r5 \
musl-dev=1.2.6-r2 \
&& pip install --no-cache-dir \
"setuptools==83.0.0" \
"wheel==0.47.0" \
"uv==0.8.13"

COPY --from=phlo-build-context /opt/phlo-build-context/wheelhouse /opt/phlo-wheelhouse

Expand All @@ -34,10 +42,19 @@ RUN set -eux; \
uv pip install --system --prerelease explicit "$PHLO_REQUIREMENT" "$PHLO_API_REQUIREMENT"; \
else \
uv pip install --system phlo "$PHLO_API_REQUIREMENT"; \
fi
fi && apk del .build-deps

# Do not retain uv's downloaded archives in the runtime image; they are not needed to run Phlo.
RUN rm -rf /root/.cache/uv

COPY --from=phlo-build-context /opt/phlo-build-context/phlo-api-entrypoint.sh /usr/local/bin/phlo-api-entrypoint.sh

RUN addgroup -S phlo \
&& adduser -S -G phlo -h /app -H phlo \
&& chown -R phlo:phlo /app

USER phlo

EXPOSE 4000

ENTRYPOINT ["/usr/local/bin/phlo-api-entrypoint.sh"]
Expand Down
2 changes: 1 addition & 1 deletion packages/phlo-clickhouse/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ The following environment variables can be used to configure ClickHouse:

| Variable | Default | Description |
|----------|---------|-------------|
| `CLICKHOUSE_VERSION` | `latest` | ClickHouse server version tag |
| `CLICKHOUSE_VERSION` | `26.5.6.64-alpine` | ClickHouse server version tag |
| `CLICKHOUSE_HTTP_PORT` | `8123` | ClickHouse HTTP interface port |
| `CLICKHOUSE_NATIVE_PORT` | `19000` | ClickHouse native protocol port |
| `CLICKHOUSE_METRICS_PORT` | `9363` | ClickHouse Prometheus metrics port |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ description: Initialize ClickHouse databases for data plane
category: data
default: false

image: clickhouse/clickhouse-server:${CLICKHOUSE_VERSION:-latest}
image: clickhouse/clickhouse-server:${CLICKHOUSE_VERSION:-26.5.6.64-alpine}

depends_on:
- clickhouse
Expand Down
4 changes: 2 additions & 2 deletions packages/phlo-clickhouse/src/phlo_clickhouse/service.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ description: ClickHouse analytical database for data plane
category: data
default: false

image: clickhouse/clickhouse-server:${CLICKHOUSE_VERSION:-latest}
image: clickhouse/clickhouse-server:${CLICKHOUSE_VERSION:-26.5.6.64-alpine}

compose:
restart: unless-stopped
Expand Down Expand Up @@ -44,7 +44,7 @@ compose:

env_vars:
CLICKHOUSE_VERSION:
default: "latest"
default: "26.5.6.64-alpine"
description: ClickHouse server version tag
CLICKHOUSE_HTTP_PORT:
default: 8123
Expand Down
10 changes: 10 additions & 0 deletions packages/phlo-clickhouse/tests/test_clickhouse_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,16 @@ def test_clickhouse_service_definition():
assert "clickhouse-logs:/var/log/clickhouse-server" in service_definition["compose"]["volumes"]


def test_clickhouse_service_pins_generated_image_version() -> None:
"""The generated environment must not replace the pinned image tag with latest."""
service_definition = ClickHouseServicePlugin().service_definition

assert service_definition["image"] == (
"clickhouse/clickhouse-server:${CLICKHOUSE_VERSION:-26.5.6.64-alpine}"
)
assert service_definition["env_vars"]["CLICKHOUSE_VERSION"]["default"] == ("26.5.6.64-alpine")


def test_clickhouse_service_metadata():
"""Validate ClickHouse service plugin metadata."""

Expand Down
2 changes: 1 addition & 1 deletion packages/phlo-clickstack/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ target-version = "py311"
include-package-data = true

[tool.setuptools.package-data]
phlo_clickstack = ["service.yaml"]
phlo_clickstack = ["Dockerfile", "service.yaml"]

[tool.setuptools.package-dir]
"" = "src"
Expand Down
Loading
Loading