Compare commits

..
Author SHA1 Message Date
Tim FandLéo a0c93f38ef chore: harden and update supporting services (#6788)
* chore: harden and update supporting services

* chore(compose): pin ChromaDB and ntfy images by tag and digest

chromadb/chroma:latest and a bare binwiederhier/ntfy change underneath
an existing install on the next pull, so two hosts on the same commit
can run different sidecar builds. SearXNG is already pinned this way.

Pin chroma 1.5.9 and ntfy v2.32.0 to the multi-arch index digests that
:latest currently resolves to, so nothing changes at runtime today. The
standalone GPU files carry the same references, as
test_gpu_compose_standalone.py requires. Add a test that every sidecar
image has a release tag and a digest.

---------

Co-authored-by: Léo <leograndcontact@gmail.com>
2026-10-24 17:44:07 +02:00
14 changed files with 65 additions and 31 deletions
+5 -5
View File
@@ -76,7 +76,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: "3.11"
# Byte-compile sources — catches syntax errors without installing deps.
@@ -91,7 +91,7 @@ jobs:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
persist-credentials: false
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
with:
node-version: "22.23.4"
- run: npm ci --ignore-scripts --no-audit --no-fund
@@ -151,7 +151,7 @@ jobs:
echo "docs_only=false" >> "$GITHUB_OUTPUT"
fi
- uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
- uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
if: steps.docs-check.outputs.docs_only != 'true'
with:
python-version: "3.11"
@@ -168,7 +168,7 @@ jobs:
# uv resolves the same versions as pip but installs ~5x faster, and its
# cache keeps each shard from re-downloading the whole requirement set.
- uses: astral-sh/setup-uv@18d369b23da25a81e9458561248e1aafff6a70fa # v10.3.1
- uses: astral-sh/setup-uv@c18668ad3cf93ea998bef934396af7bb5c839dc7 # v10.2.0
if: steps.docs-check.outputs.docs_only != 'true'
with:
version: "0.13.5"
@@ -180,7 +180,7 @@ jobs:
python scripts/check_ca_trust.py --python
uv pip install --system -r requirements.txt
if: steps.docs-check.outputs.docs_only != 'true'
- uses: actions/setup-node@949feb2413d6458794dcd2491c4babbbce0c15c1 # v7.1.0
- uses: actions/setup-node@820762786026740c76f36085b0efc47a31fe5020 # v7.0.0
if: steps.docs-check.outputs.docs_only != 'true'
with:
node-version: "22.23.4"
+2 -2
View File
@@ -31,11 +31,11 @@ jobs:
with:
persist-credentials: false
- name: Initialize CodeQL
uses: github/codeql-action/init@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
languages: ${{ matrix.language }}
build-mode: none
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
category: "/language:${{ matrix.language }}"
+1 -1
View File
@@ -42,7 +42,7 @@ jobs:
persist-credentials: false
- name: Lint Dockerfile
uses: hadolint/hadolint-action@06be81baf89a55ffd0e24b8f04a4185738dd3387 # v3.5.0
uses: hadolint/hadolint-action@2a66e89f53d0771bb131a7fa31f3136336094aa6 # v3.4.0
with:
dockerfile: Dockerfile
# DL3008: pinning apt package versions is impractical on a -slim base
+5 -5
View File
@@ -61,14 +61,14 @@ jobs:
persist-credentials: false
- name: Set up Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
with:
driver: docker
# Build without pushing so a broken Dockerfile is caught here, and the
# exact image we ship is what gets scanned.
- name: Build image
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
push: false
@@ -107,12 +107,12 @@ jobs:
persist-credentials: false
- name: Set up Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
with:
driver: docker
- name: Build image
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
push: false
@@ -133,7 +133,7 @@ jobs:
TRIVY_DB_REPOSITORY: ghcr.io/aquasecurity/trivy-db:2
- name: Upload Trivy results
uses: github/codeql-action/upload-sarif@9b3254085494b55a0f7c4c2b3d2d52cc712ce370 # v4.39.1
uses: github/codeql-action/upload-sarif@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4.37.7
with:
sarif_file: trivy-results.sarif
category: trivy-image
+1 -1
View File
@@ -63,7 +63,7 @@ jobs:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
+1 -1
View File
@@ -47,4 +47,4 @@ jobs:
steps:
- name: Deploy to GitHub Pages
id: deployment
uses: actions/deploy-pages@368f82528645a54fb793d4d04e342629a3f51346 # v5.0.1
uses: actions/deploy-pages@cd2ce8fcbc39b97be8ca5fce6e763baed58fa128 # v5.0.0
+5 -5
View File
@@ -55,7 +55,7 @@ jobs:
shell: bash
run: echo "IMAGE_NAME=${GITHUB_REPOSITORY,,}" >> "$GITHUB_ENV"
- name: Set up Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
@@ -64,7 +64,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build and push by digest
id: build
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
pull: true
@@ -78,7 +78,7 @@ jobs:
digest="${{ steps.build.outputs.digest }}"
touch "/tmp/digests/${digest#sha256:}"
- name: Upload digest
uses: actions/upload-artifact@cf430e030ddbb5b0abf93d22962f4752f3646cd9 # v7.0.2
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: digest-${{ matrix.arch }}
path: /tmp/digests/*
@@ -107,13 +107,13 @@ jobs:
echo "version=$v" >> "$GITHUB_OUTPUT"
echo "short=${GITHUB_SHA::7}" >> "$GITHUB_OUTPUT"
- name: Download digests
uses: actions/download-artifact@9000827ccba6bdab643e8b6fd33ac0654aef8333 # v8.0.2
uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
with:
path: /tmp/digests
pattern: digest-*
merge-multiple: true
- name: Set up Buildx
uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Log in to GHCR
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
+2 -2
View File
@@ -62,9 +62,9 @@ jobs:
env:
SEARXNG_TEST_IMAGE: ${{ steps.image.outputs.image }}
run: docker pull "$SEARXNG_TEST_IMAGE"
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- uses: docker/setup-buildx-action@37fe631027851001ddb9b187196cc803df7f5f0e # v4.3.0
- name: Check unmodified upstream trust and actual entrypoint offline
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0
with:
context: .
file: docker/searxng/Dockerfile
+1 -1
View File
@@ -66,7 +66,7 @@ jobs:
persist-credentials: false
- name: Set up Python
uses: actions/setup-python@6d4fd22b20465ccc0f7354476588e8df7c9ca0e0 # v7.1.0
uses: actions/setup-python@5fda3b95a4ea91299a34e894583c3862153e4b97 # v7.0.0
with:
python-version: '3.12'
+2 -2
View File
@@ -48,8 +48,8 @@ just composed.
| Service | Image | Purpose | License |
|---|---|---|---|
| [SearXNG](https://github.com/searxng/searxng) | `searxng/searxng:2026.9.25-12f8b6515` (pinned tag; see compose) | Default metasearch backend | AGPL-3.0 |
| [ChromaDB](https://github.com/chroma-core/chroma) | `chromadb/chroma:latest` | Vector store for memory / RAG | Apache-2.0 |
| [ntfy](https://github.com/binwiederhier/ntfy) | `binwiederhier/ntfy` | Push notifications (self-hosted reminders) | Apache-2.0 / GPL-2.0 |
| [ChromaDB](https://github.com/chroma-core/chroma) | `chromadb/chroma:1.5.9` (pinned digest; see compose) | Vector store for memory / RAG | Apache-2.0 |
| [ntfy](https://github.com/binwiederhier/ntfy) | `binwiederhier/ntfy:v2.32.0` (pinned digest; see compose) | Push notifications (self-hosted reminders) | Apache-2.0 / GPL-2.0 |
## Bundled front-end libraries
+2 -2
View File
@@ -127,7 +127,7 @@ services:
- ${RENDER_GID:-render}
chromadb:
image: docker.io/chromadb/chroma:latest
image: docker.io/chromadb/chroma:1.5.9@sha256:1e0b73a187a28757c572acba508c46f48c9e8b0acaf5c20e6d95cdedce1acdf6
ports:
- "${CHROMADB_BIND:-127.0.0.1}:8100:8000"
volumes:
@@ -196,7 +196,7 @@ services:
restart: unless-stopped
ntfy:
image: docker.io/binwiederhier/ntfy
image: docker.io/binwiederhier/ntfy:v2.32.0@sha256:59250e1d8be2ee2186c4560322f625faef22ace380331f05b63f8842dbd62602
command: serve
ports:
- "${NTFY_BIND:-127.0.0.1}:8091:80"
+2 -2
View File
@@ -130,7 +130,7 @@ services:
capabilities: [gpu]
chromadb:
image: docker.io/chromadb/chroma:latest
image: docker.io/chromadb/chroma:1.5.9@sha256:1e0b73a187a28757c572acba508c46f48c9e8b0acaf5c20e6d95cdedce1acdf6
ports:
- "${CHROMADB_BIND:-127.0.0.1}:8100:8000"
volumes:
@@ -199,7 +199,7 @@ services:
restart: unless-stopped
ntfy:
image: docker.io/binwiederhier/ntfy
image: docker.io/binwiederhier/ntfy:v2.32.0@sha256:59250e1d8be2ee2186c4560322f625faef22ace380331f05b63f8842dbd62602
command: serve
ports:
- "${NTFY_BIND:-127.0.0.1}:8091:80"
+2 -2
View File
@@ -108,7 +108,7 @@ services:
restart: unless-stopped
chromadb:
image: docker.io/chromadb/chroma:latest
image: docker.io/chromadb/chroma:1.5.9@sha256:1e0b73a187a28757c572acba508c46f48c9e8b0acaf5c20e6d95cdedce1acdf6
ports:
- "${CHROMADB_BIND:-127.0.0.1}:8100:8000"
volumes:
@@ -177,7 +177,7 @@ services:
restart: unless-stopped
ntfy:
image: docker.io/binwiederhier/ntfy
image: docker.io/binwiederhier/ntfy:v2.32.0@sha256:59250e1d8be2ee2186c4560322f625faef22ace380331f05b63f8842dbd62602
command: serve
ports:
- "${NTFY_BIND:-127.0.0.1}:8091:80"
+34
View File
@@ -0,0 +1,34 @@
"""Sidecar images must be pinned to a versioned tag and an immutable digest.
A bare or ``:latest`` reference changes underneath an existing install on the
next ``docker compose pull``/``up``, so two hosts on the same commit can run
different ChromaDB or ntfy builds. The ``odysseus`` service is excluded: it is
the project's own image, selected through ``ODYSSEUS_IMAGE``. The standalone
GPU files are held equal to these services by test_gpu_compose_standalone.py.
"""
import re
from pathlib import Path
import pytest
import yaml
COMPOSE = Path(__file__).resolve().parents[1] / "docker-compose.yml"
PINNED = re.compile(r"^[\w.\-/]+:(?P<tag>[\w.\-]+)@sha256:[0-9a-f]{64}$")
def _sidecars():
services = yaml.safe_load(COMPOSE.read_text(encoding="utf-8"))["services"]
return {name: svc["image"] for name, svc in services.items() if name != "odysseus"}
def test_expected_sidecars_present():
assert {"chromadb", "searxng", "ntfy"} <= set(_sidecars())
@pytest.mark.parametrize("name", sorted(_sidecars()))
def test_sidecar_image_has_version_tag_and_digest(name):
image = _sidecars()[name]
match = PINNED.match(image)
assert match, f"{name}: expected <repo>:<tag>@sha256:<digest>, got {image!r}"
assert match.group("tag") != "latest", f"{name}: pin a release tag, not latest"