From 3c263a708d001cf6dc331ad72ac397c8e986c225 Mon Sep 17 00:00:00 2001 From: IliesAllali Date: Fri, 9 Oct 2026 14:24:19 +0200 Subject: [PATCH] =?UTF-8?q?Docker=20:=20images=20API=20et=20front=20publi?= =?UTF-8?q?=C3=A9es=20sur=20ghcr.io,=20installation=20sans=20cloner=20le?= =?UTF-8?q?=20d=C3=A9p=C3=B4t?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Après chaque CI verte sur main (et sur un tag vX.Y.Z), le workflow Images construit parenthese-api et parenthese-web en amd64 et arm64, les pousse sur ghcr.io, puis teste une installation dans un dossier vide avec docker-compose.yml et .env pour seuls fichiers. docker-compose.yml tire ces images (PARENTHESE_VERSION, latest par défaut) et garde le build depuis les sources en repli. Demande faite sur r/GenealogieFR. Co-Authored-By: Claude Opus 5.5 --- .env.example | 5 ++ .github/workflows/images.yml | 149 +++++++++++++++++++++++++++++++++++ README.fr.md | 14 +++- README.md | 14 +++- docker-compose.yml | 5 ++ 5 files changed, 183 insertions(+), 4 deletions(-) create mode 100644 .github/workflows/images.yml diff --git a/.env.example b/.env.example index c6dcdee..f6b17af 100644 --- a/.env.example +++ b/.env.example @@ -34,6 +34,11 @@ CORS_ORIGIN=http://localhost # MEDIA_QUOTA_PER_TREE_MB=5120 # MEDIA_PENDING_QUOTA_PER_TREE_MB=500 +# ── Images ──────────────────────────────────────────────────────────────────── +# Version des images ghcr.io/iliesallali/parenthese-api et parenthese-web : latest (dernière +# version de main), ou une version figée, par exemple 1.0. +# PARENTHESE_VERSION=latest + # ── Web ─────────────────────────────────────────────────────────────────────── # Port publié sur la machine hôte (80 = http://localhost sans préciser de port). WEB_PORT=80 diff --git a/.github/workflows/images.yml b/.github/workflows/images.yml new file mode 100644 index 0000000..9e10e15 --- /dev/null +++ b/.github/workflows/images.yml @@ -0,0 +1,149 @@ +name: Images + +# Publie les images Docker de l'API et du front sur ghcr.io, pour un auto-hébergement sans le code +# source : docker-compose.yml + .env suffisent. +# - après une CI réussie sur un push sur main : tags `latest` et `sha-` +# - sur un tag de version vX.Y.Z : tags `X.Y.Z` et `X.Y` +# Puis l'installation est testée dans un dossier vide, avec ces deux seuls fichiers. +on: + workflow_run: + workflows: + - CI + types: + - completed + branches: + - main + push: + tags: + - 'v*' + workflow_dispatch: + +permissions: + contents: read + +concurrency: + group: images-${{ github.event.workflow_run.head_sha || github.ref }} + cancel-in-progress: false + +jobs: + publish: + name: Build and push (${{ matrix.image }}) + if: >- + github.repository == 'IliesAllali/parenthese' && + ( + github.event_name != 'workflow_run' || + ( + github.event.workflow_run.conclusion == 'success' && + github.event.workflow_run.event == 'push' && + github.event.workflow_run.head_repository.full_name == 'IliesAllali/parenthese' + ) + ) + runs-on: ubuntu-latest + timeout-minutes: 60 + permissions: + contents: read + packages: write + strategy: + fail-fast: false + matrix: + include: + - image: parenthese-api + context: backend + file: backend/Dockerfile + - image: parenthese-web + context: . + file: Dockerfile.web + steps: + # Sur workflow_run, le commit exact que la CI a validé, pas la tête de main au moment du run. + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} + persist-credentials: false + + - name: Short commit + id: commit + run: echo "sha=$(git rev-parse --short=7 HEAD)" >> "$GITHUB_OUTPUT" + + - uses: docker/setup-qemu-action@99012661954931238ded8c8b007157a8430204e1 # v4.4.0 + + - uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1 + + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Tags and labels + id: meta + uses: docker/metadata-action@dc802804100637a589fabce1cb79ff13a1411302 # v6.2.0 + with: + images: ghcr.io/iliesallali/${{ matrix.image }} + tags: | + type=semver,pattern={{version}} + type=semver,pattern={{major}}.{{minor}} + type=raw,value=latest,enable=${{ github.event_name == 'workflow_run' }} + type=raw,value=sha-${{ steps.commit.outputs.sha }} + labels: | + org.opencontainers.image.revision=${{ github.event.workflow_run.head_sha || github.sha }} + org.opencontainers.image.licenses=PolyForm-Noncommercial-1.0.0 + + - name: Build and push + uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0 + with: + context: ${{ matrix.context }} + file: ${{ matrix.file }} + platforms: linux/amd64,linux/arm64 + push: true + tags: ${{ steps.meta.outputs.tags }} + labels: ${{ steps.meta.outputs.labels }} + cache-from: type=gha,scope=${{ matrix.image }} + cache-to: type=gha,mode=max,scope=${{ matrix.image }} + + compose-only: + name: Install from the images only + needs: publish + runs-on: ubuntu-latest + timeout-minutes: 20 + permissions: + contents: read + packages: read + steps: + - uses: actions/checkout@fbc6f3992d24b796d5a048ff273f7fcc4a7b6c09 # v5.1.0 + with: + ref: ${{ github.event.workflow_run.head_sha || github.sha }} + path: src + persist-credentials: false + + # Tant que les paquets ne sont pas publics, le pull demande une connexion. + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Empty folder with docker-compose.yml and .env only + run: | + set -euo pipefail + mkdir parenthese + cp src/docker-compose.yml src/.env.example parenthese/ + cd parenthese + cp .env.example .env + sed -i "s|^POSTGRES_PASSWORD=.*|POSTGRES_PASSWORD=$(openssl rand -hex 24)|" .env + sed -i "s|^JWT_SECRET=.*|JWT_SECRET=$(openssl rand -base64 48)|" .env + echo "PARENTHESE_VERSION=sha-$(git -C ../src rev-parse --short=7 HEAD)" >> .env + ls -A + + - name: docker compose up -d (pull, no build) + working-directory: parenthese + run: docker compose up -d --pull always --no-build --wait --wait-timeout 300 + + - name: Smoke test through the web port + run: bash src/tools/docker-smoke.sh http://localhost + + - name: Containers and logs on failure + if: failure() + working-directory: parenthese + run: | + docker compose ps -a + docker compose logs --no-color --timestamps diff --git a/README.fr.md b/README.fr.md index b77a9af..03d06c1 100644 --- a/README.fr.md +++ b/README.fr.md @@ -32,6 +32,14 @@ cd parenthese cp .env.example .env ``` +Le clone est facultatif. Les images de l'API et du front sont publiées sur le registre de GitHub (`ghcr.io/iliesallali/parenthese-api` et `ghcr.io/iliesallali/parenthese-web`, en amd64 et arm64), deux fichiers suffisent donc : + +```bash +mkdir parenthese && cd parenthese +curl -fsSLO https://raw.githubusercontent.com/IliesAllali/parenthese/main/docker-compose.yml +curl -fsSL https://raw.githubusercontent.com/IliesAllali/parenthese/main/.env.example -o .env +``` + Ouvrez `.env` et remplissez les deux valeurs vides : - `POSTGRES_PASSWORD` : un mot de passe pour la base (lettres et chiffres). @@ -43,7 +51,7 @@ Puis : docker compose up -d ``` -Le premier démarrage construit les images, applique les migrations de la base, puis sert l'application sur [http://localhost](http://localhost). Créez votre compte depuis l'écran d'accueil : le premier compte est un compte comme les autres, il n'y a pas d'administrateur global. +Le premier démarrage télécharge les images (ou les construit depuis les sources si elles sont introuvables), applique les migrations de la base, puis sert l'application sur [http://localhost](http://localhost). Créez votre compte depuis l'écran d'accueil : le premier compte est un compte comme les autres, il n'y a pas d'administrateur global. Trois conteneurs tournent : `db` (PostgreSQL 16), `api` (l'API Node) et `web` (nginx qui sert le front et relaie l'API). Seul `web` est exposé, sur le port choisi par `WEB_PORT` (80 par défaut). Si ce port est déjà pris, changez `WEB_PORT` dans `.env` et adaptez `CORS_ORIGIN` en conséquence. @@ -88,9 +96,11 @@ Le script `deploy/backup.sh` fait la même chose (dump PostgreSQL, archive des m ### Mettre à jour ```bash -git pull && docker compose up -d --build +docker compose pull && docker compose up -d ``` +`latest` suit la branche main. Pour rester sur une version, renseignez `PARENTHESE_VERSION` dans `.env` (par exemple `1.0`). Depuis un clone, `git pull && docker compose up -d --build` reconstruit depuis les sources. + Les migrations de base de données s'appliquent toutes seules au démarrage de l'API. Faites une sauvegarde avant, par habitude. ## Développer diff --git a/README.md b/README.md index 9f1ff19..218661e 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,14 @@ cd parenthese cp .env.example .env ``` +You can also skip the clone. The API and front-end images are published on GitHub's registry (`ghcr.io/iliesallali/parenthese-api` and `ghcr.io/iliesallali/parenthese-web`, for amd64 and arm64), so two files are enough: + +```bash +mkdir parenthese && cd parenthese +curl -fsSLO https://raw.githubusercontent.com/IliesAllali/parenthese/main/docker-compose.yml +curl -fsSL https://raw.githubusercontent.com/IliesAllali/parenthese/main/.env.example -o .env +``` + Open `.env` and fill in the two empty values: - `POSTGRES_PASSWORD`: a password for the database (letters and digits only). @@ -49,7 +57,7 @@ Then: docker compose up -d ``` -The first start builds the images, applies the database migrations, then serves the app on [http://localhost](http://localhost). Create your account from the home screen. The first account is an account like any other, there is no global administrator. +The first start downloads the images (or builds them from the source when they can't be pulled), applies the database migrations, then serves the app on [http://localhost](http://localhost). Create your account from the home screen. The first account is an account like any other, there is no global administrator. Three containers run: `db` (PostgreSQL 16), `api` (the Node API) and `web` (nginx, which serves the front end and proxies the API). Only `web` is exposed, on the port set by `WEB_PORT` (80 by default). If that port is taken, change `WEB_PORT` in `.env` and update `CORS_ORIGIN` to match. @@ -94,9 +102,11 @@ The `deploy/backup.sh` script does the same (PostgreSQL dump, media archive, che ### Updating ```bash -git pull && docker compose up -d --build +docker compose pull && docker compose up -d ``` +`latest` follows the main branch. To stay on a version, set `PARENTHESE_VERSION` in `.env` (for example `1.0`). From a clone, `git pull && docker compose up -d --build` builds from the source instead. + Database migrations apply on their own when the API starts. Make a backup first, as a habit. ## Development diff --git a/docker-compose.yml b/docker-compose.yml index 6332f84..341bb3e 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -4,6 +4,9 @@ # docker compose logs -f api suivre l'API # docker compose down arrêter (les volumes db-data et media sont conservés) # bash tools/docker-smoke.sh vérifier l'installation +# Les images de l'API et du front sont publiées sur ghcr.io (version choisie par PARENTHESE_VERSION, +# `latest` par défaut) : ce fichier et le .env suffisent, sans le code. Si l'image est introuvable, +# compose la construit depuis les sources (dépôt cloné) ; `--build` force cette construction. name: parenthese @@ -24,6 +27,7 @@ services: retries: 12 api: + image: ghcr.io/iliesallali/parenthese-api:${PARENTHESE_VERSION:-latest} build: ./backend restart: unless-stopped depends_on: @@ -49,6 +53,7 @@ services: start_period: 30s web: + image: ghcr.io/iliesallali/parenthese-web:${PARENTHESE_VERSION:-latest} build: context: . dockerfile: Dockerfile.web