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
34 changes: 34 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Git
.git/
.gitignore
.gitattributes

# CI / IDE / harness metadata
.github/
.vscode/
.idea/
.claude/

# Python / uv caches and virtualenvs
.venv/
__pycache__/
*.py[cod]
*$py.class
.pytest_cache/
.mypy_cache/
.ruff_cache/
.coverage
htmlcov/

# Large or unrelated repo content
DIMA_V7.pdf
article/
plugin/
misp/

# Docs & licensing not needed at build time
README.md
LICENSE

# Test outputs
_regen_misp/
114 changes: 114 additions & 0 deletions .github/workflows/docker.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
name: Docker image

on:
push:
branches: [main]
tags: ["v*"]
Comment thread
sebdraven marked this conversation as resolved.
pull_request:
paths:
- "docker/**"
- ".github/workflows/docker.yml"
- "src/**"
Comment thread
sebdraven marked this conversation as resolved.
- "docs/**"
- "DETECT/**"
- "INFORM/**"
- "MEMORISE/**"
- "ACT/**"
- "pyproject.toml"
- "uv.lock"
workflow_dispatch:

permissions:
contents: read
packages: write

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository }}

jobs:
runtime:
name: Runtime image (nginx)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up QEMU
uses: docker/setup-qemu-action@v3

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
tags: |
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix=sha-,enable=${{ github.event_name == 'workflow_dispatch' }}
Comment thread
sebdraven marked this conversation as resolved.

Comment thread
sebdraven marked this conversation as resolved.
- name: Build (and push on tag or main)
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
target: runtime
Comment thread
sebdraven marked this conversation as resolved.
platforms: linux/amd64,linux/arm64
push: ${{ github.event_name != 'pull_request' && steps.meta.outputs.tags != '' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Comment thread
sebdraven marked this conversation as resolved.
cache-from: type=gha,scope=runtime
cache-to: type=gha,mode=max,scope=runtime

tests:
name: Test image (pytest)
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v4

- name: Set up Docker Buildx
uses: docker/setup-buildx-action@v3

- name: Log in to GHCR
if: github.event_name != 'pull_request'
uses: docker/login-action@v3
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata
id: meta
uses: docker/metadata-action@v5
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}
flavor: suffix=-tests,onlatest=true
tags: |
type=ref,event=tag
type=raw,value=latest,enable={{is_default_branch}}
type=sha,prefix=sha-,enable=${{ github.event_name == 'workflow_dispatch' }}

- name: Build (linux/amd64 only) and push
uses: docker/build-push-action@v6
with:
context: .
file: docker/Dockerfile
target: tests
platforms: linux/amd64
push: ${{ github.event_name != 'pull_request' && steps.meta.outputs.tags != '' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}
Comment thread
sebdraven marked this conversation as resolved.
cache-from: type=gha,scope=tests
cache-to: type=gha,mode=max,scope=tests
1 change: 0 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ env/

# uv
.uv/
uv.lock

# Tests / outils
.pytest_cache/
Expand Down
57 changes: 57 additions & 0 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# syntax=docker/dockerfile:1.6

# ---- Stage 1: regenerate the per-phase JSONs from the Markdown sources ----
# Uses the official uv image (Python 3.12 on Alpine) so the same toolchain
# is used in every stage. The converter itself depends on stdlib only,
# so we invoke python directly via `uv run --no-project`.
FROM ghcr.io/astral-sh/uv:python3.12-alpine AS builder
WORKDIR /build

COPY src/dima_convert.py ./src/dima_convert.py
COPY DETECT/ ./DETECT/
COPY INFORM/ ./INFORM/
COPY MEMORISE/ ./MEMORISE/
COPY ACT/ ./ACT/
COPY docs/ ./docs/

RUN uv run --no-project python src/dima_convert.py md2json --all -o docs/data

# ---- Stage 2: pytest runner (build standalone with --target tests) ----
# Pytest version comes from pyproject.toml's [dependency-groups].dev,
# so the build stays reproducible (no implicit pytest latest).
FROM ghcr.io/astral-sh/uv:python3.12-alpine AS tests
WORKDIR /app

COPY pyproject.toml uv.lock ./
COPY src/ ./src/
COPY DETECT/ ./DETECT/
COPY INFORM/ ./INFORM/
COPY MEMORISE/ ./MEMORISE/
COPY ACT/ ./ACT/

# Synchronise the dev group (pytest) into /app/.venv with the exact
# versions resolved in uv.lock (--frozen). The image is reproducible:
# rebuilding from the same commit always pulls the same pytest.
RUN uv sync --group dev --frozen

# Run the suite at build time so `docker build --target tests` fails on regression.
RUN uv run pytest

# And keep the same entrypoint for interactive runs (docker run --rm <image>).
CMD ["uv", "run", "pytest", "-v"]

# ---- Stage 3: nginx runtime (default target) ----
FROM nginxinc/nginx-unprivileged:alpine AS runtime

LABEL org.opencontainers.image.title="DIMA Navigator" \
org.opencontainers.image.description="Static SPA serving the DIMA matrix (Detect/Inform/Memorise/Act)." \
org.opencontainers.image.source="https://github.com/M82-project/DIMA" \
org.opencontainers.image.licenses="see repository"

COPY docker/nginx.conf /etc/nginx/conf.d/default.conf
COPY --from=builder /build/docs /usr/share/nginx/html

EXPOSE 8080

HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD wget -qO- http://localhost:8080/ >/dev/null || exit 1
45 changes: 45 additions & 0 deletions docker/nginx.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
server {
listen 8080 default_server;
server_name _;

root /usr/share/nginx/html;
index index.html;

# Active la compression text/json/css/js
gzip on;
gzip_vary on;
gzip_min_length 256;
gzip_proxied any;
gzip_types
text/plain
text/css
text/javascript
application/javascript
application/json
image/svg+xml;

# HTML et JSON : pas de cache long, pour qu'un redeploiement
# se voie immediatement.
location ~* \.(html|json)$ {
add_header Cache-Control "no-cache, must-revalidate";
try_files $uri =404;
}

# Assets versionnes (fonts, images) : cache long.
location ~* \.(woff2?|ttf|otf|png|jpg|jpeg|gif|webp|svg|ico)$ {
add_header Cache-Control "public, max-age=2592000, immutable";
try_files $uri =404;
}

location / {
Comment thread
sebdraven marked this conversation as resolved.
try_files $uri $uri/ /index.html;
# Le fallback SPA sert /index.html sur des routes type /campaign/123 :
# comme la location regex ci-dessus ne matche pas (pas de .html dans
# l'URL), il faut redonner le no-cache ici pour que les redeploys
# soient visibles tout de suite.
add_header Cache-Control "no-cache, must-revalidate" always;
}

# Pas d'index automatique d'un repertoire sans index.html.
autoindex off;
}
Loading
Loading