From 90b0e1bf2c9cc0b07d7933e20aba1b6606dd5c53 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Tue, 4 Aug 2026 10:24:53 +0100 Subject: [PATCH 01/11] docs(prj4): explain Day 1 registry choice, configuration and auth verification --- docs/prj4/cicd-architecture.md | 77 ++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) create mode 100644 docs/prj4/cicd-architecture.md diff --git a/docs/prj4/cicd-architecture.md b/docs/prj4/cicd-architecture.md new file mode 100644 index 0000000..1f353ef --- /dev/null +++ b/docs/prj4/cicd-architecture.md @@ -0,0 +1,77 @@ +# Architecture CI/CD — Jour 1 : registre et accès + +Ce document explique, pas à pas et en langage clair, ce qui a été mis en place et pourquoi. Il sera complété au fil des jours. + +## 1. Choix du registre Docker (tâche 1) + +**Décision : GitHub Container Registry (GHCR, `ghcr.io`).** + +Un registre Docker, c'est l'endroit où on stocke les images construites, pour que d'autres machines (ici, le VPS) puissent les télécharger (`docker pull`) sans avoir besoin de reconstruire l'image elles-mêmes. + +Pourquoi GHCR et pas un autre (Docker Hub, etc.) : +- Le dépôt est déjà sur GitHub → pas de nouveau compte à créer, pas de nouveau système d'authentification à apprendre. +- L'intégration avec les GitHub Actions (notre CI) est native. +- C'est exactement ce que le brief recommande : "utiliser le registre intégré à la plateforme Git". + +## 2. Configuration du registre (tâche 2) + +Sur GHCR, il n'y a **pas de bouton "activer"** à chercher : le package (l'image stockée) se crée tout seul dès le premier `docker push`. Mais deux réglages ont dû être vérifiés/décidés : + +### a) Les permissions du token de CI + +Chaque run de GitHub Actions dispose d'un jeton automatique, `GITHUB_TOKEN`, valable seulement le temps du run. En vérifiant les réglages du dépôt, j'ai trouvé que ce token a par défaut uniquement la permission **lecture** (`default_workflow_permissions: read`). + +Conséquence concrète : si on essaie de faire un `docker push` depuis un job GitHub Actions sans rien changer, ça échouera avec une erreur "permission denied". **Il faudra donc, au Jour 2, ajouter explicitement dans le job de build/push :** + +```yaml +permissions: + packages: write +``` + +C'est une bonne pratique de sécurité (le principe du "moindre privilège" : chaque job ne demande que les droits dont il a vraiment besoin), donc on ne change rien au réglage global du dépôt — on l'autorise juste, job par job, là où c'est nécessaire. + +### b) La visibilité du package (image privée ou publique) + +**Décision : privée.** + +Une image Docker peut contenir des détails qu'on ne veut pas rendre publics (structure interne, dépendances précises, etc.), même si le code source, lui, est public. On a choisi que l'image reste privée : ça veut dire que pour la télécharger (`docker pull`), il faut être authentifié — y compris pour le VPS qui va la récupérer au moment du déploiement. + +C'est cohérent avec ce que le brief attend : il liste explicitement des variables `REGISTRY_USER` / `REGISTRY_PASSWORD`, ce qui suppose qu'une authentification est nécessaire quelque part dans la chaîne. + +## 3. Vérification de l'authentification (tâche 3) + +Ici, il fallait distinguer **deux mécanismes d'authentification différents**, qui servent à deux moments différents : + +| Qui s'authentifie | Avec quoi | Quand | Pourquoi | +|---|---|---|---| +| Le pipeline CI (GitHub Actions), pour **pousser** l'image | `GITHUB_TOKEN` automatique | Uniquement pendant l'exécution du job | Ce token existe et vit seulement le temps du run — inutile de le stocker, GitHub le fournit à chaque fois | +| Le VPS, pour **récupérer** (`pull`) l'image au moment du déploiement | Un **Personal Access Token (PAT)** créé manuellement sur GitHub | À chaque déploiement, depuis une machine externe (le VPS) | Le VPS n'est pas un run GitHub Actions : il n'a pas de `GITHUB_TOKEN`. Il lui faut un identifiant à lui, qui dure dans le temps | + +### Ce qui a été testé concrètement + +Un PAT classique a été créé (scope `write:packages`, qui inclut aussi la lecture) et testé en conditions réelles, depuis ce poste de travail (pour simuler ce que fera le VPS plus tard) : + +1. `docker login ghcr.io` avec le PAT → connexion acceptée +2. `docker build` de l'image du projet, taguée `ghcr.io/ronaldo-f-dev/kps-tasks-api:auth-check` +3. `docker push` → **réussi** (preuve que le PAT a bien le droit d'écrire sur le registre) +4. Suppression de l'image en local, puis `docker pull` du même tag → **réussi** (preuve que le PAT a bien le droit de lire/télécharger, comme devra le faire le VPS) +5. Vérification via l'API GitHub que le package `kps-tasks-api` est bien créé et **privé**, comme décidé au point 2b + +Une fois ce test validé, le fichier local contenant le token a été supprimé, et la session Docker locale déconnectée (`docker logout`) — le PAT ne doit pas traîner sur la machine plus longtemps que nécessaire. + +### Secrets stockés dans GitHub Actions + +Ces trois valeurs sont maintenant enregistrées dans *Settings → Secrets and variables → Actions* du dépôt (jamais visibles en clair, ni dans le code, ni dans les logs) : + +- `REGISTRY_URL` = `ghcr.io` +- `REGISTRY_USER` = `Ronaldo-F-dev` +- `REGISTRY_PASSWORD` = le PAT créé ci-dessus + +Ces variables serviront au Jour 2 (le job de la CI qui pousse l'image) et au Jour 3 (le script `deploy.sh` qui, exécuté sur ou vers le VPS, doit lui aussi s'authentifier pour faire le `pull`). + +## Suite (tâches 4 et +) + +- Tâche 4 : liste complète des variables CI/CD nécessaires (couvert en partie ci-dessus, à compléter dans `docs/prj4/ci-cd-variables.md`) +- Tâche 5-7 : utilisateur de déploiement sur le VPS, accès SSH depuis le pipeline, répertoire applicatif +- Tâche 8-9 : schéma d'architecture de déploiement complet +- Tâche 10 : vérifier que le VPS peut lui-même faire un `pull` (avec le PAT stocké) From 55f4897973e77fc1f6a4e5d1f8d022080716d562 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Tue, 4 Aug 2026 10:53:26 +0100 Subject: [PATCH 02/11] docs(prj4): explain where the pushed image actually lives (GitHub Packages) --- docs/prj4/cicd-architecture.md | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/docs/prj4/cicd-architecture.md b/docs/prj4/cicd-architecture.md index 1f353ef..d41a9ad 100644 --- a/docs/prj4/cicd-architecture.md +++ b/docs/prj4/cicd-architecture.md @@ -38,6 +38,17 @@ Une image Docker peut contenir des détails qu'on ne veut pas rendre publics (st C'est cohérent avec ce que le brief attend : il liste explicitement des variables `REGISTRY_USER` / `REGISTRY_PASSWORD`, ce qui suppose qu'une authentification est nécessaire quelque part dans la chaîne. +### c) Où voir l'image concrètement + +Point important à bien comprendre : **l'image Docker n'est pas un fichier du dépôt Git**. On ne la trouvera jamais en naviguant dans les fichiers du repo sur GitHub — elle vit dans une section séparée de GitHub appelée **Packages**. + +- Sur le profil GitHub, onglet **Packages** : https://github.com/Ronaldo-F-dev?tab=packages +- Lien direct vers le package de ce projet : https://github.com/users/Ronaldo-F-dev/packages/container/package/kps-tasks-api + +Comme le package est **privé** (décision du point 2b), seul le compte `Ronaldo-F-dev` (connecté) peut le voir dans l'interface GitHub — ce n'est pas visible publiquement, même si le code source du dépôt, lui, est public. Pour que le VPS puisse le récupérer plus tard, il devra s'authentifier avec le PAT (voir tâche 3 ci-dessous), exactement comme n'importe quel utilisateur externe. + +Chaque version poussée de l'image apparaît comme un **tag** sur cette page (ex. `auth-check` pour l'instant — le tag de test créé pendant la vérification ci-dessous). Au Jour 2, ces tags seront remplacés par la vraie stratégie de versioning (SHA de commit, version Git). + ## 3. Vérification de l'authentification (tâche 3) Ici, il fallait distinguer **deux mécanismes d'authentification différents**, qui servent à deux moments différents : From 4ef8da00949124aadc69309d46d493fe62243502 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Tue, 4 Aug 2026 11:28:29 +0100 Subject: [PATCH 03/11] ci: build, tag (commit SHA + version) and push Docker image to GHCR --- .github/workflows/ci.yml | 48 ++++++++++++++++++++++++++++++++++++---- 1 file changed, 44 insertions(+), 4 deletions(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 08b11a4..8c6e32a 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -5,6 +5,8 @@ on: branches: - main - feature/** + tags: + - "v*" pull_request: branches: - main @@ -45,18 +47,56 @@ jobs: pip install --only-binary :all: pytest httpx - run: pytest - build: - name: Build Docker image + docker_build: + name: Build and push Docker image runs-on: ubuntu-latest needs: test + permissions: + contents: read + packages: write + steps: - uses: actions/checkout@v4 + - name: Compute image tags + id: tags + run: | + IMAGE="ghcr.io/${{ github.repository_owner }}/kps-tasks-api" + IMAGE=$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]') + COMMIT_TAG="$IMAGE:commit-${GITHUB_SHA::7}" + echo "image=$IMAGE" >> "$GITHUB_OUTPUT" + echo "commit_tag=$COMMIT_TAG" >> "$GITHUB_OUTPUT" + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then + echo "version_tag=$IMAGE:${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" + else + echo "version_tag=" >> "$GITHUB_OUTPUT" + fi + - name: Build Docker image - #run: docker build -t kps-task . - run: docker build -t kps-tasks-api:ci-${GITHUB_SHA::7} . + run: | + docker build -t "${{ steps.tags.outputs.commit_tag }}" . + if [ -n "${{ steps.tags.outputs.version_tag }}" ]; then + docker tag "${{ steps.tags.outputs.commit_tag }}" "${{ steps.tags.outputs.version_tag }}" + fi + + - name: Log in to GitHub Container Registry + uses: docker/login-action@v3 + with: + registry: ghcr.io + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + + - name: Push image + run: | + docker push "${{ steps.tags.outputs.commit_tag }}" + if [ -n "${{ steps.tags.outputs.version_tag }}" ]; then + docker push "${{ steps.tags.outputs.version_tag }}" + fi + + - name: Verify image is available in the registry + run: docker manifest inspect "${{ steps.tags.outputs.commit_tag }}" > /dev/null && echo "OK: ${{ steps.tags.outputs.commit_tag }} is available" secret_scan: name: Gitleaks secret scan From 618a0393ddf04dfbc9db33b1060765c803855616 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Tue, 4 Aug 2026 11:30:31 +0100 Subject: [PATCH 04/11] fix: quote colon in echo string breaking YAML parsing --- .github/workflows/ci.yml | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 8c6e32a..f1d9621 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -96,7 +96,9 @@ jobs: fi - name: Verify image is available in the registry - run: docker manifest inspect "${{ steps.tags.outputs.commit_tag }}" > /dev/null && echo "OK: ${{ steps.tags.outputs.commit_tag }} is available" + run: | + docker manifest inspect "${{ steps.tags.outputs.commit_tag }}" > /dev/null + echo "Image available: ${{ steps.tags.outputs.commit_tag }}" secret_scan: name: Gitleaks secret scan From d64fa363e3b91669b997fd8e1858bcff0c5335c3 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Tue, 4 Aug 2026 13:20:54 +0100 Subject: [PATCH 05/11] docs(prj4): document image tagging strategy and Day 2 intermediate questions --- docs/prj4/image-versioning.md | 49 +++++++++++++++++++++++++++++ docs/prj4/intermediate-questions.md | 31 ++++++++++++++++++ 2 files changed, 80 insertions(+) create mode 100644 docs/prj4/image-versioning.md create mode 100644 docs/prj4/intermediate-questions.md diff --git a/docs/prj4/image-versioning.md b/docs/prj4/image-versioning.md new file mode 100644 index 0000000..4c7443d --- /dev/null +++ b/docs/prj4/image-versioning.md @@ -0,0 +1,49 @@ +# Stratégie de tagging des images Docker + +## Deux tags, deux usages différents + +Chaque image construite par la CI reçoit un tag basé sur le commit, systématiquement. Un second tag, basé sur la version, est ajouté **seulement** quand le commit correspond à un tag Git. + +| Tag | Format | Quand | Usage | +|---|---|---|---| +| Commit | `commit-` | À chaque push (branche ou PR) | Traçabilité totale : chaque run de CI produit une image identifiable, utile pour tester une branche précise ou déboguer | +| Version | `vX.Y.Z` | Seulement quand un tag Git `vX.Y.Z` existe sur ce commit | Référence stable pour un déploiement en production — une version, un sens, pas d'ambiguïté | + +Exemple réel produit par ce projet : + +``` +ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-618a039 +``` + +## Pourquoi pas `latest` + +Le brief l'interdit comme référence principale de production, et c'est volontaire : `latest` ne dit rien sur la version réellement déployée — c'est juste "la dernière poussée à un moment donné", qui change de sens à chaque nouveau push. Si un déploiement utilise `latest`, il est impossible de savoir avec certitude quelle version tourne réellement, ni de revenir en arrière de façon fiable. C'est directement contraire à un des objectifs du projet : pouvoir tracer et restaurer une version précise (rollback, Jour 4). + +## Comment le tag est calculé dans la CI + +Extrait de `.github/workflows/ci.yml` (job `docker_build`) : + +```yaml +- name: Compute image tags + id: tags + run: | + IMAGE="ghcr.io/${{ github.repository_owner }}/kps-tasks-api" + IMAGE=$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]') + COMMIT_TAG="$IMAGE:commit-${GITHUB_SHA::7}" + echo "commit_tag=$COMMIT_TAG" >> "$GITHUB_OUTPUT" + if [ "${GITHUB_REF_TYPE}" = "tag" ]; then + echo "version_tag=$IMAGE:${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT" + else + echo "version_tag=" >> "$GITHUB_OUTPUT" + fi +``` + +- `GITHUB_SHA::7` : les 7 premiers caractères du SHA du commit (assez pour être unique en pratique, plus lisible qu'un SHA complet). +- `GITHUB_REF_TYPE` / `GITHUB_REF_NAME` : GitHub Actions les renseigne automatiquement — quand le workflow est déclenché par un tag Git (`v*`, ajouté aux déclencheurs `on: push: tags:`), `GITHUB_REF_TYPE` vaut `tag` et `GITHUB_REF_NAME` contient son nom exact (`v1.0.1`, par exemple). +- Le nom du propriétaire du dépôt (`github.repository_owner`) est mis en minuscules : GHCR exige des noms d'image en minuscules, alors qu'un compte GitHub peut contenir des majuscules (`Ronaldo-F-dev`). + +## Ce que ça donne à l'usage + +- Un push normal sur une branche → une seule image, taguée par son commit. +- Un `git tag v1.0.1 && git push origin v1.0.1` → le pipeline se redéclenche (le tag Git fait partie des déclencheurs), et l'image obtient **en plus** le tag `v1.0.1`, pointant vers exactement le même contenu que le tag commit correspondant. +- Le déploiement en production (Jour 3) utilisera toujours le tag de version, jamais le tag de commit ni `latest`. diff --git a/docs/prj4/intermediate-questions.md b/docs/prj4/intermediate-questions.md new file mode 100644 index 0000000..2c5ca37 --- /dev/null +++ b/docs/prj4/intermediate-questions.md @@ -0,0 +1,31 @@ +# Questions intermédiaires — Projet 4 + +Ce document regroupe, jour par jour, les réponses aux "questions intermédiaires" du brief. Il se remplit au fur et à mesure de l'avancement. + +## Jour 2 — Build et push d'une image versionnée + +### 21. Pourquoi tagger une image Docker ? + +Sans tag explicite, Docker utilise `latest` par défaut — un nom qui ne dit rien sur le contenu réel de l'image. Tagger permet d'identifier précisément *quel* code tourne dans *quelle* image : indispensable pour déployer une version précise, revenir en arrière (rollback) sur une version connue, ou déboguer un problème en sachant exactement ce qui a été construit et quand. + +### 22. Pourquoi éviter `latest` en production ? + +Parce que `latest` change de sens à chaque nouveau push : ce n'est pas une version, c'est juste "la dernière image poussée à un instant donné". Un déploiement basé sur `latest` ne peut pas garantir de façon fiable quelle version tourne réellement, et un rollback vers "l'image d'avant" devient impossible à cibler précisément puisqu'il n'y a pas de référence stable à restaurer. + +### 23. Quelle est la différence entre un tag Git et un tag Docker ? + +- Un **tag Git** (`v1.0.0`) marque un point précis dans l'historique du **code source**. +- Un **tag Docker** (`commit-618a039`, `v1.0.0`) identifie une **image construite**, c'est-à-dire un artefact binaire (le code + ses dépendances + son environnement d'exécution, déjà assemblés). + +Le lien entre les deux se fait par convention dans ce projet : quand un tag Git `vX.Y.Z` existe, l'image Docker correspondante reçoit le même nom comme tag. Mais rien n'oblige les deux à être synchronisés — on peut très bien construire une image sans qu'aucun tag Git n'existe (c'est le cas à chaque push normal, d'où le tag `commit-`). + +### 24. Pourquoi construire l'image en CI plutôt que sur le serveur ? + +- **Reproductibilité** : la CI construit toujours dans un environnement propre et identique (voir `docs/prj3/docker-build-local-vs-ci.md`) — pas de dépendance à l'état particulier d'un serveur. +- **Séparation des responsabilités** : le serveur de production sert à *faire tourner* l'application, pas à la *compiler*. Lui donner des outils de build (compilateurs, dépendances de développement) élargit inutilement sa surface d'attaque. +- **Traçabilité** : chaque build en CI est lié à un commit précis et laisse des logs consultables ; un build fait à la main sur un serveur ne laisse aucune trace fiable. +- **Rapidité de déploiement** : le serveur n'a plus qu'à faire un `docker pull` d'une image déjà prête, au lieu d'attendre une compilation complète à chaque déploiement. + +### 25. Comment vérifie-t-on qu'une image est bien dans le registre ? + +Dans ce projet, la CI le vérifie elle-même juste après le push, avec `docker manifest inspect ` : cette commande interroge le registre et échoue si l'image n'y est pas — donc si le job passe, l'image est confirmée présente (voir l'étape "Verify image is available in the registry" du job `docker_build`). On peut aussi le vérifier manuellement : soit avec `docker pull ` depuis n'importe quelle machine authentifiée, soit visuellement sur la page du package GitHub (`docs/prj4/cicd-architecture.md`, section "Où voir l'image concrètement"). From ecb13712e5389d62cf3ef4820c0799e44bbe4fa9 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Tue, 4 Aug 2026 13:21:26 +0100 Subject: [PATCH 06/11] ci: introduce intentional COPY typo to test a failing docker_build job --- Dockerfile | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 34964fa..9c252fe 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ COPY --from=builder /wheels /wheels RUN pip install --no-cache-dir --only-binary :all: --no-index --find-links=/wheels -r requirements.txt && \ rm -rf /wheels -COPY --chown=appuser:appuser app /app/app +COPY --chown=appuser:appuser app-typo /app/app USER appuser From 67e62a2137c50071b4a7cd6470e8fb6b1abb4d20 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Tue, 4 Aug 2026 13:24:10 +0100 Subject: [PATCH 07/11] fix: restore correct COPY path, capture failing build log as evidence --- Dockerfile | 2 +- evidence/image-build-failed.txt | 74 +++++++++++++++++++++++++++++++++ 2 files changed, 75 insertions(+), 1 deletion(-) create mode 100644 evidence/image-build-failed.txt diff --git a/Dockerfile b/Dockerfile index 9c252fe..34964fa 100644 --- a/Dockerfile +++ b/Dockerfile @@ -24,7 +24,7 @@ COPY --from=builder /wheels /wheels RUN pip install --no-cache-dir --only-binary :all: --no-index --find-links=/wheels -r requirements.txt && \ rm -rf /wheels -COPY --chown=appuser:appuser app-typo /app/app +COPY --chown=appuser:appuser app /app/app USER appuser diff --git a/evidence/image-build-failed.txt b/evidence/image-build-failed.txt new file mode 100644 index 0000000..2aec704 --- /dev/null +++ b/evidence/image-build-failed.txt @@ -0,0 +1,74 @@ +2026-08-04T12:22:30.8541668Z ##[group]Run docker build -t "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-ecb1371" . +2026-08-04T12:22:30.8542414Z ^[[36;1mdocker build -t "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-ecb1371" .^[[0m +2026-08-04T12:22:30.8542901Z ^[[36;1mif [ -n "" ]; then^[[0m +2026-08-04T12:22:30.8543324Z ^[[36;1m docker tag "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-ecb1371" ""^[[0m +2026-08-04T12:22:30.8543759Z ^[[36;1mfi^[[0m +2026-08-04T12:22:30.8585987Z shell: /usr/bin/bash -e {0} +2026-08-04T12:22:30.8586311Z ##[endgroup] +2026-08-04T12:22:33.5309573Z #0 building with "default" instance using docker driver +2026-08-04T12:22:33.5310231Z +2026-08-04T12:22:33.5316133Z #1 [internal] load build definition from Dockerfile +2026-08-04T12:22:33.5316717Z #1 transferring dockerfile: 803B 0.0s done +2026-08-04T12:22:33.5317207Z #1 DONE 0.0s +2026-08-04T12:22:33.5317370Z +2026-08-04T12:22:33.5317652Z #2 [internal] load metadata for docker.io/library/python:3.12-slim +2026-08-04T12:22:33.7218336Z #2 ... +2026-08-04T12:22:33.7218768Z +2026-08-04T12:22:33.7219308Z #3 [auth] library/python:pull token for registry-1.docker.io +2026-08-04T12:22:33.7219943Z #3 DONE 0.0s +2026-08-04T12:22:33.8722708Z +2026-08-04T12:22:33.8723486Z #2 [internal] load metadata for docker.io/library/python:3.12-slim +2026-08-04T12:22:34.1926287Z #2 DONE 0.8s +2026-08-04T12:22:34.2539639Z +2026-08-04T12:22:34.2540260Z #4 [internal] load .dockerignore +2026-08-04T12:22:34.2540816Z #4 transferring context: 190B done +2026-08-04T12:22:34.2541705Z #4 DONE 0.0s +2026-08-04T12:22:34.2541877Z +2026-08-04T12:22:34.2542019Z #5 [internal] load build context +2026-08-04T12:22:34.2542554Z #5 transferring context: 227B done +2026-08-04T12:22:34.2542998Z #5 DONE 0.0s +2026-08-04T12:22:34.2543148Z +2026-08-04T12:22:34.2543266Z #6 [builder 2/4] WORKDIR /build +2026-08-04T12:22:34.2543558Z #6 CACHED +2026-08-04T12:22:34.2543695Z +2026-08-04T12:22:34.2544221Z #7 [builder 4/4] RUN python -m pip install --upgrade pip && pip wheel --only-binary :all: --wheel-dir /wheels -r requirements.txt +2026-08-04T12:22:34.2544984Z #7 CACHED +2026-08-04T12:22:34.2545349Z +2026-08-04T12:22:34.2545660Z #8 [stage-1 3/7] RUN useradd --create-home --shell /usr/sbin/nologin appuser +2026-08-04T12:22:34.2546181Z #8 CACHED +2026-08-04T12:22:34.2546326Z +2026-08-04T12:22:34.2546462Z #9 [builder 3/4] COPY requirements.txt ./ +2026-08-04T12:22:34.2546894Z #9 CACHED +2026-08-04T12:22:34.2547102Z +2026-08-04T12:22:34.2547279Z #10 [stage-1 5/7] COPY --from=builder /wheels /wheels +2026-08-04T12:22:34.2547695Z #10 CACHED +2026-08-04T12:22:34.2547856Z +2026-08-04T12:22:34.2548409Z #11 [stage-1 6/7] RUN pip install --no-cache-dir --only-binary :all: --no-index --find-links=/wheels -r requirements.txt && rm -rf /wheels +2026-08-04T12:22:34.2549191Z #11 CACHED +2026-08-04T12:22:34.2549322Z +2026-08-04T12:22:34.2549445Z #12 [stage-1 2/7] WORKDIR /app +2026-08-04T12:22:34.2549740Z #12 CACHED +2026-08-04T12:22:34.2549900Z +2026-08-04T12:22:34.2550275Z #13 [stage-1 4/7] COPY requirements.txt ./ +2026-08-04T12:22:34.2550623Z #13 CACHED +2026-08-04T12:22:34.2550766Z +2026-08-04T12:22:34.2550980Z #14 [stage-1 7/7] COPY --chown=appuser:appuser app-typo /app/app +2026-08-04T12:22:34.2554876Z #14 ERROR: failed to calculate checksum of ref dce1e4a9-fc26-4218-aaf0-feee5194c8cb::ey2gpaxgrhll2k6s3ah9uetds: "/app-typo": not found +2026-08-04T12:22:34.2555637Z +2026-08-04T12:22:34.2556036Z #15 [builder 1/4] FROM docker.io/library/python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de +2026-08-04T12:22:34.2556864Z #15 resolve docker.io/library/python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de done +2026-08-04T12:22:34.2557578Z #15 sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de 10.37kB / 10.37kB done +2026-08-04T12:22:34.2558018Z #15 CANCELED +2026-08-04T12:22:34.2558199Z ------ +2026-08-04T12:22:34.2558472Z > [stage-1 7/7] COPY --chown=appuser:appuser app-typo /app/app: +2026-08-04T12:22:34.2558828Z ------ +2026-08-04T12:22:34.2663441Z Dockerfile:27 +2026-08-04T12:22:34.2663868Z -------------------- +2026-08-04T12:22:34.2664441Z 25 | rm -rf /wheels +2026-08-04T12:22:34.2664714Z 26 | +2026-08-04T12:22:34.2665003Z 27 | >>> COPY --chown=appuser:appuser app-typo /app/app +2026-08-04T12:22:34.2752546Z 28 | +2026-08-04T12:22:34.2752997Z 29 | USER appuser +2026-08-04T12:22:34.2753781Z -------------------- +2026-08-04T12:22:34.2755373Z ERROR: failed to build: failed to solve: failed to compute cache key: failed to calculate checksum of ref dce1e4a9-fc26-4218-aaf0-feee5194c8cb::ey2gpaxgrhll2k6s3ah9uetds: "/app-typo": not found +2026-08-04T12:22:34.2770253Z ##[error]Process completed with exit code 1. From 0a419dde3c8cf9f277263a06822ecb5888a301d5 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Tue, 4 Aug 2026 13:28:17 +0100 Subject: [PATCH 08/11] docs: capture successful image build and registry push evidence --- evidence/image-build.txt | 481 +++++++++++++++++++++++++++++++++++++ evidence/registry-push.txt | 40 +++ 2 files changed, 521 insertions(+) create mode 100644 evidence/image-build.txt create mode 100644 evidence/registry-push.txt diff --git a/evidence/image-build.txt b/evidence/image-build.txt new file mode 100644 index 0000000..a95e781 --- /dev/null +++ b/evidence/image-build.txt @@ -0,0 +1,481 @@ +2026-08-04T12:25:07.8800244Z Current runner version: '2.336.0' +2026-08-04T12:25:07.8825297Z ##[group]Runner Image Provisioner +2026-08-04T12:25:07.8826145Z Hosted Compute Agent +2026-08-04T12:25:07.8826845Z Version: 20260707.563 +2026-08-04T12:25:07.8827520Z Commit: 02667638d2b423fbc733a8e32a88b44996a3ba6e +2026-08-04T12:25:07.8828289Z Build Date: 2026-07-07T19:33:50Z +2026-08-04T12:25:07.8828979Z Worker ID: {e02c8c0c-5686-4d10-ad1c-7bcd6d04634c} +2026-08-04T12:25:07.8829676Z Azure Region: westus +2026-08-04T12:25:07.8830280Z ##[endgroup] +2026-08-04T12:25:07.8831771Z ##[group]Operating System +2026-08-04T12:25:07.8832752Z Ubuntu +2026-08-04T12:25:07.8833380Z 24.04.4 +2026-08-04T12:25:07.8833892Z LTS +2026-08-04T12:25:07.8834427Z ##[endgroup] +2026-08-04T12:25:07.8835045Z ##[group]Runner Image +2026-08-04T12:25:07.8835678Z Image: ubuntu-24.04 +2026-08-04T12:25:07.8836320Z Version: 20260720.247.2 +2026-08-04T12:25:07.8837374Z Included Software: https://github.com/actions/runner-images/blob/ubuntu24/20260720.247/images/ubuntu/Ubuntu2404-Readme.md +2026-08-04T12:25:07.8839236Z Image Release: https://github.com/actions/runner-images/releases/tag/ubuntu24%2F20260720.247 +2026-08-04T12:25:07.8840185Z ##[endgroup] +2026-08-04T12:25:07.8841404Z ##[group]GITHUB_TOKEN Permissions +2026-08-04T12:25:07.8843444Z Contents: read +2026-08-04T12:25:07.8844554Z Metadata: read +2026-08-04T12:25:07.8845174Z Packages: write +2026-08-04T12:25:07.8845721Z ##[endgroup] +2026-08-04T12:25:07.8847824Z Secret source: Actions +2026-08-04T12:25:07.8848882Z Prepare workflow directory +2026-08-04T12:25:07.9185144Z Prepare all required actions +2026-08-04T12:25:07.9238732Z Getting action download info +2026-08-04T12:25:08.2839656Z Download action repository 'actions/checkout@v4' (SHA:11d5960a326750d5838078e36cf38b85af677262) +2026-08-04T12:25:08.7563428Z Download action repository 'docker/login-action@v3' (SHA:c94ce9fb468520275223c153574b00df6fe4bcc9) +2026-08-04T12:25:09.4820353Z Complete job name: Build and push Docker image +2026-08-04T12:25:09.5824165Z Node 20 is being deprecated. This workflow is running with Node 24 by default. If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ +2026-08-04T12:25:09.5836474Z ##[group]Run actions/checkout@v4 +2026-08-04T12:25:09.5837819Z with: +2026-08-04T12:25:09.5838712Z repository: Ronaldo-F-dev/devops-prj3 +2026-08-04T12:25:09.5848640Z token: *** +2026-08-04T12:25:09.5849497Z ssh-strict: true +2026-08-04T12:25:09.5850388Z ssh-user: git +2026-08-04T12:25:09.5851255Z persist-credentials: true +2026-08-04T12:25:09.5852434Z clean: true +2026-08-04T12:25:09.5853330Z sparse-checkout-cone-mode: true +2026-08-04T12:25:09.5854374Z fetch-depth: 1 +2026-08-04T12:25:09.5855211Z fetch-tags: false +2026-08-04T12:25:09.5856081Z show-progress: true +2026-08-04T12:25:09.5856955Z lfs: false +2026-08-04T12:25:09.5857776Z submodules: false +2026-08-04T12:25:09.5858698Z set-safe-directory: true +2026-08-04T12:25:09.5859750Z allow-unsafe-pr-checkout: false +2026-08-04T12:25:09.5861096Z ##[endgroup] +2026-08-04T12:25:09.6893375Z Syncing repository: Ronaldo-F-dev/devops-prj3 +2026-08-04T12:25:09.6897545Z ##[group]Getting Git version info +2026-08-04T12:25:09.6899859Z Working directory is '/home/runner/work/devops-prj3/devops-prj3' +2026-08-04T12:25:09.6903460Z [command]/usr/bin/git version +2026-08-04T12:25:09.6958702Z git version 2.54.0 +2026-08-04T12:25:09.6983217Z ##[endgroup] +2026-08-04T12:25:09.7015506Z Temporarily overriding HOME='/home/runner/work/_temp/a9e3ce12-25c8-4789-9075-3018b7b3967d' before making global git config changes +2026-08-04T12:25:09.7021050Z Adding repository directory to the temporary git global config as a safe directory +2026-08-04T12:25:09.7025822Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/devops-prj3/devops-prj3 +2026-08-04T12:25:09.7070877Z Deleting the contents of '/home/runner/work/devops-prj3/devops-prj3' +2026-08-04T12:25:09.7075866Z ##[group]Initializing the repository +2026-08-04T12:25:09.7080801Z [command]/usr/bin/git init /home/runner/work/devops-prj3/devops-prj3 +2026-08-04T12:25:09.7189124Z hint: Using 'master' as the name for the initial branch. This default branch name +2026-08-04T12:25:09.7192934Z hint: will change to "main" in Git 3.0. To configure the initial branch name +2026-08-04T12:25:09.7196192Z hint: to use in all of your new repositories, which will suppress this warning, +2026-08-04T12:25:09.7200034Z hint: call: +2026-08-04T12:25:09.7203077Z hint: +2026-08-04T12:25:09.7204680Z hint: git config --global init.defaultBranch +2026-08-04T12:25:09.7206948Z hint: +2026-08-04T12:25:09.7208852Z hint: Names commonly chosen instead of 'master' are 'main', 'trunk' and +2026-08-04T12:25:09.7212196Z hint: 'development'. The just-created branch can be renamed via this command: +2026-08-04T12:25:09.7214747Z hint: +2026-08-04T12:25:09.7216072Z hint: git branch -m +2026-08-04T12:25:09.7221602Z hint: +2026-08-04T12:25:09.7224001Z hint: Disable this message with "git config set advice.defaultBranchName false" +2026-08-04T12:25:09.7227600Z Initialized empty Git repository in /home/runner/work/devops-prj3/devops-prj3/.git/ +2026-08-04T12:25:09.7233413Z [command]/usr/bin/git remote add origin https://github.com/Ronaldo-F-dev/devops-prj3 +2026-08-04T12:25:09.7245974Z ##[endgroup] +2026-08-04T12:25:09.7248585Z ##[group]Disabling automatic garbage collection +2026-08-04T12:25:09.7250915Z [command]/usr/bin/git config --local gc.auto 0 +2026-08-04T12:25:09.7281899Z ##[endgroup] +2026-08-04T12:25:09.7284404Z ##[group]Setting up auth +2026-08-04T12:25:09.7288388Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand +2026-08-04T12:25:09.7330495Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" +2026-08-04T12:25:09.7700609Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader +2026-08-04T12:25:09.7750003Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :" +2026-08-04T12:25:09.7988516Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir: +2026-08-04T12:25:09.8025469Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url +2026-08-04T12:25:09.8249575Z [command]/usr/bin/git config --local http.https://github.com/.extraheader AUTHORIZATION: basic *** +2026-08-04T12:25:09.8288830Z ##[endgroup] +2026-08-04T12:25:09.8290339Z ##[group]Fetching the repository +2026-08-04T12:25:09.8299734Z [command]/usr/bin/git -c protocol.version=2 fetch --no-tags --prune --no-recurse-submodules --depth=1 origin +67e62a2137c50071b4a7cd6470e8fb6b1abb4d20:refs/remotes/origin/feature/registry-and-cicd-variables +2026-08-04T12:25:10.2408129Z From https://github.com/Ronaldo-F-dev/devops-prj3 +2026-08-04T12:25:10.2411212Z * [new ref] 67e62a2137c50071b4a7cd6470e8fb6b1abb4d20 -> origin/feature/registry-and-cicd-variables +2026-08-04T12:25:10.2443941Z ##[endgroup] +2026-08-04T12:25:10.2445423Z ##[group]Determining the checkout info +2026-08-04T12:25:10.2446931Z ##[endgroup] +2026-08-04T12:25:10.2453367Z [command]/usr/bin/git sparse-checkout disable +2026-08-04T12:25:10.2502823Z [command]/usr/bin/git config --local --unset-all extensions.worktreeConfig +2026-08-04T12:25:10.2534848Z ##[group]Checking out the ref +2026-08-04T12:25:10.2539770Z [command]/usr/bin/git checkout --progress --force -B feature/registry-and-cicd-variables refs/remotes/origin/feature/registry-and-cicd-variables +2026-08-04T12:25:10.2632715Z Switched to a new branch 'feature/registry-and-cicd-variables' +2026-08-04T12:25:10.2637270Z branch 'feature/registry-and-cicd-variables' set up to track 'origin/feature/registry-and-cicd-variables'. +2026-08-04T12:25:10.2642485Z ##[endgroup] +2026-08-04T12:25:10.2685499Z [command]/usr/bin/git log -1 --format=%H +2026-08-04T12:25:10.2711912Z 67e62a2137c50071b4a7cd6470e8fb6b1abb4d20 +2026-08-04T12:25:10.2979931Z ##[group]Run IMAGE="ghcr.io/Ronaldo-F-dev/kps-tasks-api" +2026-08-04T12:25:10.2981360Z ^[[36;1mIMAGE="ghcr.io/Ronaldo-F-dev/kps-tasks-api"^[[0m +2026-08-04T12:25:10.2983087Z ^[[36;1mIMAGE=$(echo "$IMAGE" | tr '[:upper:]' '[:lower:]')^[[0m +2026-08-04T12:25:10.2984451Z ^[[36;1mCOMMIT_TAG="$IMAGE:commit-${GITHUB_SHA::7}"^[[0m +2026-08-04T12:25:10.2985645Z ^[[36;1mecho "image=$IMAGE" >> "$GITHUB_OUTPUT"^[[0m +2026-08-04T12:25:10.2986882Z ^[[36;1mecho "commit_tag=$COMMIT_TAG" >> "$GITHUB_OUTPUT"^[[0m +2026-08-04T12:25:10.2988139Z ^[[36;1mif [ "${GITHUB_REF_TYPE}" = "tag" ]; then^[[0m +2026-08-04T12:25:10.2989521Z ^[[36;1m echo "version_tag=$IMAGE:${GITHUB_REF_NAME}" >> "$GITHUB_OUTPUT"^[[0m +2026-08-04T12:25:10.2990825Z ^[[36;1melse^[[0m +2026-08-04T12:25:10.2991641Z ^[[36;1m echo "version_tag=" >> "$GITHUB_OUTPUT"^[[0m +2026-08-04T12:25:10.2992822Z ^[[36;1mfi^[[0m +2026-08-04T12:25:10.3040468Z shell: /usr/bin/bash -e {0} +2026-08-04T12:25:10.3041380Z ##[endgroup] +2026-08-04T12:25:10.3228845Z ##[group]Run docker build -t "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2" . +2026-08-04T12:25:10.3230786Z ^[[36;1mdocker build -t "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2" .^[[0m +2026-08-04T12:25:10.3232566Z ^[[36;1mif [ -n "" ]; then^[[0m +2026-08-04T12:25:10.3233830Z ^[[36;1m docker tag "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2" ""^[[0m +2026-08-04T12:25:10.3235168Z ^[[36;1mfi^[[0m +2026-08-04T12:25:10.3276409Z shell: /usr/bin/bash -e {0} +2026-08-04T12:25:10.3277404Z ##[endgroup] +2026-08-04T12:25:10.8213234Z #0 building with "default" instance using docker driver +2026-08-04T12:25:10.8214242Z +2026-08-04T12:25:10.8214478Z #1 [internal] load build definition from Dockerfile +2026-08-04T12:25:10.8214924Z #1 transferring dockerfile: 798B done +2026-08-04T12:25:10.8215281Z #1 DONE 0.0s +2026-08-04T12:25:10.8215443Z +2026-08-04T12:25:10.8215690Z #2 [internal] load metadata for docker.io/library/python:3.12-slim +2026-08-04T12:25:11.0520037Z #2 ... +2026-08-04T12:25:11.0520606Z +2026-08-04T12:25:11.0521133Z #3 [auth] library/python:pull token for registry-1.docker.io +2026-08-04T12:25:11.0521763Z #3 DONE 0.0s +2026-08-04T12:25:11.2012218Z +2026-08-04T12:25:11.2013227Z #2 [internal] load metadata for docker.io/library/python:3.12-slim +2026-08-04T12:25:11.5129223Z #2 DONE 0.8s +2026-08-04T12:25:11.6349850Z +2026-08-04T12:25:11.6350620Z #4 [internal] load .dockerignore +2026-08-04T12:25:11.6351348Z #4 transferring context: 190B done +2026-08-04T12:25:11.6351959Z #4 DONE 0.0s +2026-08-04T12:25:11.6352531Z +2026-08-04T12:25:11.6352713Z #5 [internal] load build context +2026-08-04T12:25:11.6353293Z #5 transferring context: 8.14kB done +2026-08-04T12:25:11.6353760Z #5 DONE 0.0s +2026-08-04T12:25:11.6353928Z +2026-08-04T12:25:11.6354610Z #6 [builder 1/4] FROM docker.io/library/python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de +2026-08-04T12:25:11.6356126Z #6 resolve docker.io/library/python:3.12-slim@sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de done +2026-08-04T12:25:11.6357484Z #6 sha256:57cd7c3a7a273101a6485ba99423ee568157882804b1124b4dd04266317710de 10.37kB / 10.37kB done +2026-08-04T12:25:11.6358704Z #6 sha256:cab2dbf575e971934a81e4622f5aba17aa7929719bd7e31033a3a83b97fd0464 1.75kB / 1.75kB done +2026-08-04T12:25:11.6359928Z #6 sha256:25c5b8011a3425a140bf5fa73be0feabd3c0d5b323eecb19dc02437a368ae075 5.66kB / 5.66kB done +2026-08-04T12:25:11.6361013Z #6 sha256:062e450697faa5f02a3a74eba9864ee4d79bc9cfbd65769fc6cdff2c05c6a053 2.10MB / 29.78MB 0.1s +2026-08-04T12:25:11.6362111Z #6 sha256:98db2485a0d07a8914586b02387e3813aa7e9fed79ab252898d3e96e21c717ea 0B / 1.29MB 0.1s +2026-08-04T12:25:11.6363093Z #6 sha256:48347b15c85fd6dde9c5b0259f378fbaee3ce231b30a42f2f2bcc4ea0285cbc9 0B / 12.11MB 0.1s +2026-08-04T12:25:11.7490455Z #6 sha256:062e450697faa5f02a3a74eba9864ee4d79bc9cfbd65769fc6cdff2c05c6a053 29.78MB / 29.78MB 0.2s done +2026-08-04T12:25:11.7491905Z #6 sha256:98db2485a0d07a8914586b02387e3813aa7e9fed79ab252898d3e96e21c717ea 1.29MB / 1.29MB 0.2s done +2026-08-04T12:25:11.7493973Z #6 sha256:fd079632edc0ab4e9d10c77ec348d5057a976e6fc508e93855548096dec2ae1e 0B / 250B 0.2s +2026-08-04T12:25:11.7495358Z #6 extracting sha256:062e450697faa5f02a3a74eba9864ee4d79bc9cfbd65769fc6cdff2c05c6a053 +2026-08-04T12:25:11.8523169Z #6 sha256:48347b15c85fd6dde9c5b0259f378fbaee3ce231b30a42f2f2bcc4ea0285cbc9 12.11MB / 12.11MB 0.3s done +2026-08-04T12:25:11.8524963Z #6 sha256:fd079632edc0ab4e9d10c77ec348d5057a976e6fc508e93855548096dec2ae1e 250B / 250B 0.3s done +2026-08-04T12:25:12.7920497Z #6 extracting sha256:062e450697faa5f02a3a74eba9864ee4d79bc9cfbd65769fc6cdff2c05c6a053 1.0s done +2026-08-04T12:25:12.7922623Z #6 extracting sha256:98db2485a0d07a8914586b02387e3813aa7e9fed79ab252898d3e96e21c717ea +2026-08-04T12:25:13.0509959Z #6 extracting sha256:98db2485a0d07a8914586b02387e3813aa7e9fed79ab252898d3e96e21c717ea 0.1s done +2026-08-04T12:25:16.0692758Z #6 extracting sha256:48347b15c85fd6dde9c5b0259f378fbaee3ce231b30a42f2f2bcc4ea0285cbc9 +2026-08-04T12:25:16.6956974Z #6 extracting sha256:48347b15c85fd6dde9c5b0259f378fbaee3ce231b30a42f2f2bcc4ea0285cbc9 0.5s done +2026-08-04T12:25:16.6959292Z #6 extracting sha256:fd079632edc0ab4e9d10c77ec348d5057a976e6fc508e93855548096dec2ae1e done +2026-08-04T12:25:16.6960614Z #6 DONE 5.2s +2026-08-04T12:25:16.8715610Z +2026-08-04T12:25:16.8716113Z #7 [builder 2/4] WORKDIR /build +2026-08-04T12:25:16.8717670Z #7 DONE 0.0s +2026-08-04T12:25:16.8717908Z +2026-08-04T12:25:16.8718083Z #8 [stage-1 2/7] WORKDIR /app +2026-08-04T12:25:16.8718509Z #8 DONE 0.0s +2026-08-04T12:25:16.8718731Z +2026-08-04T12:25:16.8718916Z #9 [builder 3/4] COPY requirements.txt ./ +2026-08-04T12:25:16.8719721Z #9 DONE 0.0s +2026-08-04T12:25:16.8720448Z +2026-08-04T12:25:16.8735262Z #10 [builder 4/4] RUN python -m pip install --upgrade pip && pip wheel --only-binary :all: --wheel-dir /wheels -r requirements.txt +2026-08-04T12:25:16.9254985Z #10 ... +2026-08-04T12:25:16.9255289Z +2026-08-04T12:25:16.9255738Z #11 [stage-1 3/7] RUN useradd --create-home --shell /usr/sbin/nologin appuser +2026-08-04T12:25:16.9256353Z #11 DONE 0.2s +2026-08-04T12:25:17.0941409Z +2026-08-04T12:25:17.0942354Z #12 [stage-1 4/7] COPY requirements.txt ./ +2026-08-04T12:25:17.0943490Z #12 DONE 0.0s +2026-08-04T12:25:17.0943917Z +2026-08-04T12:25:17.0945243Z #10 [builder 4/4] RUN python -m pip install --upgrade pip && pip wheel --only-binary :all: --wheel-dir /wheels -r requirements.txt +2026-08-04T12:25:18.0605107Z #10 1.340 Requirement already satisfied: pip in /usr/local/lib/python3.12/site-packages (25.0.1) +2026-08-04T12:25:18.1659116Z #10 1.388 Collecting pip +2026-08-04T12:25:18.1661526Z #10 1.405 Downloading pip-26.2-py3-none-any.whl.metadata (4.6 kB) +2026-08-04T12:25:18.1662507Z #10 1.410 Downloading pip-26.2-py3-none-any.whl (1.8 MB) +2026-08-04T12:25:18.1663596Z #10 1.431 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.8/1.8 MB 142.7 MB/s eta 0:00:00 +2026-08-04T12:25:18.1664270Z #10 1.446 Installing collected packages: pip +2026-08-04T12:25:18.3690753Z #10 1.446 Attempting uninstall: pip +2026-08-04T12:25:18.3691367Z #10 1.448 Found existing installation: pip 25.0.1 +2026-08-04T12:25:18.3691761Z #10 1.488 Uninstalling pip-25.0.1: +2026-08-04T12:25:18.3692301Z #10 1.498 Successfully uninstalled pip-25.0.1 +2026-08-04T12:25:19.0514779Z #10 2.331 Successfully installed pip-26.2 +2026-08-04T12:25:19.2030908Z #10 2.332 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning. +2026-08-04T12:25:19.9563297Z #10 3.236 Collecting fastapi==0.141.1 (from -r requirements.txt (line 1)) +2026-08-04T12:25:20.1559518Z #10 3.258 Downloading fastapi-0.141.1-py3-none-any.whl.metadata (27 kB) +2026-08-04T12:25:20.1560828Z #10 3.277 Collecting uvicorn==0.52.1 (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:20.1562900Z #10 3.285 Downloading uvicorn-0.52.1-py3-none-any.whl.metadata (6.6 kB) +2026-08-04T12:25:20.2537113Z #10 3.533 Collecting SQLAlchemy==2.0.51 (from -r requirements.txt (line 3)) +2026-08-04T12:25:20.3746226Z #10 3.536 Downloading sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (9.5 kB) +2026-08-04T12:25:20.3747574Z #10 3.551 Collecting psycopg==3.3.4 (from psycopg[binary]==3.3.4->-r requirements.txt (line 4)) +2026-08-04T12:25:20.3748890Z #10 3.553 Downloading psycopg-3.3.4-py3-none-any.whl.metadata (4.3 kB) +2026-08-04T12:25:20.3750022Z #10 3.564 Collecting python-dotenv==1.2.2 (from -r requirements.txt (line 5)) +2026-08-04T12:25:20.3751037Z #10 3.567 Downloading python_dotenv-1.2.2-py3-none-any.whl.metadata (27 kB) +2026-08-04T12:25:20.3752247Z #10 3.654 Collecting pydantic==2.13.4 (from -r requirements.txt (line 6)) +2026-08-04T12:25:20.5280392Z #10 3.657 Downloading pydantic-2.13.4-py3-none-any.whl.metadata (109 kB) +2026-08-04T12:25:20.6731500Z #10 3.953 Collecting ruff==0.16.1 (from -r requirements.txt (line 7)) +2026-08-04T12:25:20.7759491Z #10 3.956 Downloading ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (26 kB) +2026-08-04T12:25:20.7760776Z #10 3.975 Collecting pytest==9.1.1 (from -r requirements.txt (line 8)) +2026-08-04T12:25:20.7761578Z #10 3.978 Downloading pytest-9.1.1-py3-none-any.whl.metadata (7.6 kB) +2026-08-04T12:25:20.7762631Z #10 3.988 Collecting pytest-cov==7.1.0 (from -r requirements.txt (line 9)) +2026-08-04T12:25:20.7763229Z #10 3.990 Downloading pytest_cov-7.1.0-py3-none-any.whl.metadata (32 kB) +2026-08-04T12:25:20.7763800Z #10 4.002 Collecting httpx==0.28.1 (from -r requirements.txt (line 10)) +2026-08-04T12:25:20.7764344Z #10 4.004 Downloading httpx-0.28.1-py3-none-any.whl.metadata (7.1 kB) +2026-08-04T12:25:20.7764978Z #10 4.021 Collecting starlette>=0.46.0 (from fastapi==0.141.1->-r requirements.txt (line 1)) +2026-08-04T12:25:20.7765646Z #10 4.023 Downloading starlette-1.3.1-py3-none-any.whl.metadata (6.4 kB) +2026-08-04T12:25:20.7766321Z #10 4.035 Collecting typing-extensions>=4.8.0 (from fastapi==0.141.1->-r requirements.txt (line 1)) +2026-08-04T12:25:20.7767026Z #10 4.038 Downloading typing_extensions-4.16.0-py3-none-any.whl.metadata (3.3 kB) +2026-08-04T12:25:20.7767723Z #10 4.043 Collecting typing-inspection>=0.4.2 (from fastapi==0.141.1->-r requirements.txt (line 1)) +2026-08-04T12:25:20.7768406Z #10 4.046 Downloading typing_inspection-0.4.2-py3-none-any.whl.metadata (2.6 kB) +2026-08-04T12:25:20.7768963Z #10 4.051 Collecting annotated-doc>=0.0.2 (from fastapi==0.141.1->-r requirements.txt (line 1)) +2026-08-04T12:25:20.7769498Z #10 4.056 Downloading annotated_doc-0.0.5-py3-none-any.whl.metadata (6.5 kB) +2026-08-04T12:25:20.9361755Z #10 4.067 Collecting click>=7.0 (from uvicorn==0.52.1->uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:20.9363181Z #10 4.070 Downloading click-8.4.2-py3-none-any.whl.metadata (2.6 kB) +2026-08-04T12:25:20.9364204Z #10 4.076 Collecting h11>=0.8 (from uvicorn==0.52.1->uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:20.9365237Z #10 4.078 Downloading h11-0.16.0-py3-none-any.whl.metadata (8.3 kB) +2026-08-04T12:25:20.9366111Z #10 4.216 Collecting greenlet>=1 (from SQLAlchemy==2.0.51->-r requirements.txt (line 3)) +2026-08-04T12:25:21.1016300Z #10 4.219 Downloading greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl.metadata (3.8 kB) +2026-08-04T12:25:21.1017645Z #10 4.228 Collecting annotated-types>=0.6.0 (from pydantic==2.13.4->-r requirements.txt (line 6)) +2026-08-04T12:25:21.1018677Z #10 4.231 Downloading annotated_types-0.8.0-py3-none-any.whl.metadata (15 kB) +2026-08-04T12:25:21.5573894Z #10 4.837 Collecting pydantic-core==2.46.4 (from pydantic==2.13.4->-r requirements.txt (line 6)) +2026-08-04T12:25:21.7558243Z #10 4.840 Downloading pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (6.6 kB) +2026-08-04T12:25:21.7559652Z #10 4.847 Collecting iniconfig>=1.0.1 (from pytest==9.1.1->-r requirements.txt (line 8)) +2026-08-04T12:25:21.7560320Z #10 4.850 Downloading iniconfig-2.3.0-py3-none-any.whl.metadata (2.5 kB) +2026-08-04T12:25:21.7560941Z #10 4.858 Collecting packaging>=22 (from pytest==9.1.1->-r requirements.txt (line 8)) +2026-08-04T12:25:21.7561558Z #10 4.861 Downloading packaging-26.2-py3-none-any.whl.metadata (3.5 kB) +2026-08-04T12:25:21.7562686Z #10 4.868 Collecting pluggy<2,>=1.5 (from pytest==9.1.1->-r requirements.txt (line 8)) +2026-08-04T12:25:21.7563585Z #10 4.870 Downloading pluggy-1.6.0-py3-none-any.whl.metadata (4.8 kB) +2026-08-04T12:25:21.7564578Z #10 4.882 Collecting pygments>=2.7.2 (from pytest==9.1.1->-r requirements.txt (line 8)) +2026-08-04T12:25:21.7565605Z #10 4.885 Downloading pygments-2.20.0-py3-none-any.whl.metadata (2.5 kB) +2026-08-04T12:25:21.9464156Z #10 5.226 Collecting coverage>=7.10.6 (from coverage[toml]>=7.10.6->pytest-cov==7.1.0->-r requirements.txt (line 9)) +2026-08-04T12:25:22.1390804Z #10 5.229 Downloading coverage-7.15.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (8.6 kB) +2026-08-04T12:25:22.1392544Z #10 5.243 Collecting anyio (from httpx==0.28.1->-r requirements.txt (line 10)) +2026-08-04T12:25:22.1393402Z #10 5.246 Downloading anyio-4.14.2-py3-none-any.whl.metadata (4.6 kB) +2026-08-04T12:25:22.1393949Z #10 5.257 Collecting certifi (from httpx==0.28.1->-r requirements.txt (line 10)) +2026-08-04T12:25:22.1394514Z #10 5.260 Downloading certifi-2026.7.22-py3-none-any.whl.metadata (2.5 kB) +2026-08-04T12:25:22.1395058Z #10 5.270 Collecting httpcore==1.* (from httpx==0.28.1->-r requirements.txt (line 10)) +2026-08-04T12:25:22.1395589Z #10 5.272 Downloading httpcore-1.0.9-py3-none-any.whl.metadata (21 kB) +2026-08-04T12:25:22.1396094Z #10 5.285 Collecting idna (from httpx==0.28.1->-r requirements.txt (line 10)) +2026-08-04T12:25:22.1396568Z #10 5.288 Downloading idna-3.18-py3-none-any.whl.metadata (6.1 kB) +2026-08-04T12:25:22.1397358Z #10 5.419 Collecting psycopg-binary==3.3.4 (from psycopg[binary]==3.3.4->-r requirements.txt (line 4)) +2026-08-04T12:25:22.2517177Z #10 5.422 Downloading psycopg_binary-3.3.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl.metadata (2.7 kB) +2026-08-04T12:25:22.2518919Z #10 5.447 Collecting httptools>=0.8.0 (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:22.2520508Z #10 5.450 Downloading httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (3.5 kB) +2026-08-04T12:25:22.2522403Z #10 5.478 Collecting pyyaml>=5.1 (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:22.2523964Z #10 5.481 Downloading pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (2.4 kB) +2026-08-04T12:25:22.2525693Z #10 5.531 Collecting uvloop>=0.15.1 (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:22.4089245Z #10 5.534 Downloading uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl.metadata (4.9 kB) +2026-08-04T12:25:22.4090552Z #10 5.599 Collecting watchfiles>=0.20 (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:22.4091428Z #10 5.601 Downloading watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl.metadata (4.9 kB) +2026-08-04T12:25:22.4092915Z #10 5.689 Collecting websockets>=13.0 (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:22.5200354Z #10 5.692 Downloading websockets-17.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl.metadata (6.3 kB) +2026-08-04T12:25:22.5202573Z #10 5.712 Downloading fastapi-0.141.1-py3-none-any.whl (131 kB) +2026-08-04T12:25:22.5203308Z #10 5.715 Downloading uvicorn-0.52.1-py3-none-any.whl (79 kB) +2026-08-04T12:25:22.5204224Z #10 5.717 Downloading sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (3.4 MB) +2026-08-04T12:25:22.5205646Z #10 5.732 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 3.4/3.4 MB 266.3 MB/s 0:00:00 +2026-08-04T12:25:22.5206303Z #10 5.735 Downloading psycopg-3.3.4-py3-none-any.whl (213 kB) +2026-08-04T12:25:22.5207172Z #10 5.738 Downloading python_dotenv-1.2.2-py3-none-any.whl (22 kB) +2026-08-04T12:25:22.5208068Z #10 5.741 Downloading pydantic-2.13.4-py3-none-any.whl (472 kB) +2026-08-04T12:25:22.5208975Z #10 5.745 Downloading ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (11.5 MB) +2026-08-04T12:25:22.6259547Z #10 5.800 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 11.5/11.5 MB 214.5 MB/s 0:00:00 +2026-08-04T12:25:22.6260554Z #10 5.803 Downloading pytest-9.1.1-py3-none-any.whl (386 kB) +2026-08-04T12:25:22.6261448Z #10 5.807 Downloading pytest_cov-7.1.0-py3-none-any.whl (22 kB) +2026-08-04T12:25:22.6262474Z #10 5.809 Downloading httpx-0.28.1-py3-none-any.whl (73 kB) +2026-08-04T12:25:22.6263967Z #10 5.812 Downloading psycopg_binary-3.3.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (5.2 MB) +2026-08-04T12:25:22.6267030Z #10 5.834 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 5.2/5.2 MB 252.3 MB/s 0:00:00 +2026-08-04T12:25:22.6268139Z #10 5.837 Downloading pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (2.1 MB) +2026-08-04T12:25:22.6269107Z #10 5.846 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.1/2.1 MB 261.5 MB/s 0:00:00 +2026-08-04T12:25:22.6269692Z #10 5.849 Downloading httpcore-1.0.9-py3-none-any.whl (78 kB) +2026-08-04T12:25:22.6270251Z #10 5.851 Downloading pluggy-1.6.0-py3-none-any.whl (20 kB) +2026-08-04T12:25:22.6270840Z #10 5.854 Downloading annotated_doc-0.0.5-py3-none-any.whl (5.3 kB) +2026-08-04T12:25:22.6271469Z #10 5.856 Downloading annotated_types-0.8.0-py3-none-any.whl (13 kB) +2026-08-04T12:25:22.6272340Z #10 5.859 Downloading click-8.4.2-py3-none-any.whl (119 kB) +2026-08-04T12:25:22.6273465Z #10 5.862 Downloading coverage-7.15.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (257 kB) +2026-08-04T12:25:22.6274240Z #10 5.865 Downloading greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (621 kB) +2026-08-04T12:25:22.6274902Z #10 5.869 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 621.5/621.5 kB 319.9 MB/s 0:00:00 +2026-08-04T12:25:22.6275312Z #10 5.872 Downloading h11-0.16.0-py3-none-any.whl (37 kB) +2026-08-04T12:25:22.6275869Z #10 5.875 Downloading httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (523 kB) +2026-08-04T12:25:22.6276459Z #10 5.879 Downloading iniconfig-2.3.0-py3-none-any.whl (7.5 kB) +2026-08-04T12:25:22.6276839Z #10 5.882 Downloading packaging-26.2-py3-none-any.whl (100 kB) +2026-08-04T12:25:22.6277389Z #10 5.885 Downloading pygments-2.20.0-py3-none-any.whl (1.2 MB) +2026-08-04T12:25:22.6277885Z #10 5.892 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 1.2/1.2 MB 190.1 MB/s 0:00:00 +2026-08-04T12:25:22.6278477Z #10 5.895 Downloading pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (807 kB) +2026-08-04T12:25:22.7947273Z #10 5.906 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 807.9/807.9 kB 70.2 MB/s 0:00:00 +2026-08-04T12:25:22.7949059Z #10 5.909 Downloading starlette-1.3.1-py3-none-any.whl (73 kB) +2026-08-04T12:25:22.7949792Z #10 5.912 Downloading anyio-4.14.2-py3-none-any.whl (125 kB) +2026-08-04T12:25:22.7950462Z #10 5.915 Downloading idna-3.18-py3-none-any.whl (65 kB) +2026-08-04T12:25:22.7950910Z #10 5.917 Downloading typing_extensions-4.16.0-py3-none-any.whl (45 kB) +2026-08-04T12:25:22.7951389Z #10 5.920 Downloading typing_inspection-0.4.2-py3-none-any.whl (14 kB) +2026-08-04T12:25:22.7952240Z #10 5.922 Downloading uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (4.4 MB) +2026-08-04T12:25:22.7953212Z #10 5.947 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 4.4/4.4 MB 189.7 MB/s 0:00:00 +2026-08-04T12:25:22.7953813Z #10 5.949 Downloading watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (456 kB) +2026-08-04T12:25:22.7954872Z #10 5.953 Downloading websockets-17.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (220 kB) +2026-08-04T12:25:22.7955532Z #10 5.957 Downloading certifi-2026.7.22-py3-none-any.whl (136 kB) +2026-08-04T12:25:22.7955939Z #10 6.074 Saved /wheels/fastapi-0.141.1-py3-none-any.whl +2026-08-04T12:25:22.9583174Z #10 6.075 Saved /wheels/uvicorn-0.52.1-py3-none-any.whl +2026-08-04T12:25:22.9584479Z #10 6.077 Saved /wheels/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +2026-08-04T12:25:22.9585699Z #10 6.077 Saved /wheels/psycopg-3.3.4-py3-none-any.whl +2026-08-04T12:25:22.9586485Z #10 6.078 Saved /wheels/python_dotenv-1.2.2-py3-none-any.whl +2026-08-04T12:25:22.9587212Z #10 6.078 Saved /wheels/pydantic-2.13.4-py3-none-any.whl +2026-08-04T12:25:22.9588151Z #10 6.082 Saved /wheels/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +2026-08-04T12:25:22.9589141Z #10 6.083 Saved /wheels/pytest-9.1.1-py3-none-any.whl +2026-08-04T12:25:22.9589867Z #10 6.083 Saved /wheels/pytest_cov-7.1.0-py3-none-any.whl +2026-08-04T12:25:22.9591012Z #10 6.084 Saved /wheels/httpx-0.28.1-py3-none-any.whl +2026-08-04T12:25:22.9623060Z #10 6.086 Saved /wheels/psycopg_binary-3.3.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl +2026-08-04T12:25:22.9624382Z #10 6.087 Saved /wheels/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +2026-08-04T12:25:22.9625268Z #10 6.087 Saved /wheels/httpcore-1.0.9-py3-none-any.whl +2026-08-04T12:25:22.9625859Z #10 6.088 Saved /wheels/pluggy-1.6.0-py3-none-any.whl +2026-08-04T12:25:22.9626452Z #10 6.088 Saved /wheels/annotated_doc-0.0.5-py3-none-any.whl +2026-08-04T12:25:22.9627080Z #10 6.089 Saved /wheels/annotated_types-0.8.0-py3-none-any.whl +2026-08-04T12:25:22.9627676Z #10 6.090 Saved /wheels/click-8.4.2-py3-none-any.whl +2026-08-04T12:25:22.9628590Z #10 6.090 Saved /wheels/coverage-7.15.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +2026-08-04T12:25:22.9629783Z #10 6.091 Saved /wheels/greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl +2026-08-04T12:25:22.9630599Z #10 6.092 Saved /wheels/h11-0.16.0-py3-none-any.whl +2026-08-04T12:25:22.9631501Z #10 6.092 Saved /wheels/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +2026-08-04T12:25:22.9635216Z #10 6.093 Saved /wheels/iniconfig-2.3.0-py3-none-any.whl +2026-08-04T12:25:22.9635882Z #10 6.093 Saved /wheels/packaging-26.2-py3-none-any.whl +2026-08-04T12:25:22.9640096Z #10 6.094 Saved /wheels/pygments-2.20.0-py3-none-any.whl +2026-08-04T12:25:22.9640736Z #10 6.095 Saved /wheels/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +2026-08-04T12:25:22.9641322Z #10 6.096 Saved /wheels/starlette-1.3.1-py3-none-any.whl +2026-08-04T12:25:22.9641669Z #10 6.096 Saved /wheels/anyio-4.14.2-py3-none-any.whl +2026-08-04T12:25:22.9642248Z #10 6.097 Saved /wheels/idna-3.18-py3-none-any.whl +2026-08-04T12:25:22.9643493Z #10 6.097 Saved /wheels/typing_extensions-4.16.0-py3-none-any.whl +2026-08-04T12:25:22.9644131Z #10 6.098 Saved /wheels/typing_inspection-0.4.2-py3-none-any.whl +2026-08-04T12:25:22.9644697Z #10 6.099 Saved /wheels/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl +2026-08-04T12:25:22.9645379Z #10 6.100 Saved /wheels/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl +2026-08-04T12:25:22.9646046Z #10 6.101 Saved /wheels/websockets-17.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl +2026-08-04T12:25:22.9646587Z #10 6.101 Saved /wheels/certifi-2026.7.22-py3-none-any.whl +2026-08-04T12:25:22.9646881Z #10 DONE 6.2s +2026-08-04T12:25:23.0848791Z +2026-08-04T12:25:23.0849331Z #13 [stage-1 5/7] COPY --from=builder /wheels /wheels +2026-08-04T12:25:23.2710893Z #13 DONE 0.0s +2026-08-04T12:25:23.2711291Z +2026-08-04T12:25:23.2712366Z #14 [stage-1 6/7] RUN pip install --no-cache-dir --only-binary :all: --no-index --find-links=/wheels -r requirements.txt && rm -rf /wheels +2026-08-04T12:25:24.3724297Z #14 1.252 Looking in links: /wheels +2026-08-04T12:25:24.4737843Z #14 1.261 Processing /wheels/fastapi-0.141.1-py3-none-any.whl (from -r requirements.txt (line 1)) +2026-08-04T12:25:24.4738892Z #14 1.271 Processing /wheels/uvicorn-0.52.1-py3-none-any.whl (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:24.4740494Z #14 1.275 Processing /wheels/sqlalchemy-2.0.51-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (from -r requirements.txt (line 3)) +2026-08-04T12:25:24.4742543Z #14 1.289 Processing /wheels/psycopg-3.3.4-py3-none-any.whl (from psycopg[binary]==3.3.4->-r requirements.txt (line 4)) +2026-08-04T12:25:24.4743882Z #14 1.296 Processing /wheels/python_dotenv-1.2.2-py3-none-any.whl (from -r requirements.txt (line 5)) +2026-08-04T12:25:24.4745063Z #14 1.299 Processing /wheels/pydantic-2.13.4-py3-none-any.whl (from -r requirements.txt (line 6)) +2026-08-04T12:25:24.4746448Z #14 1.305 Processing /wheels/ruff-0.16.1-py3-none-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (from -r requirements.txt (line 7)) +2026-08-04T12:25:24.4748245Z #14 1.320 Processing /wheels/pytest-9.1.1-py3-none-any.whl (from -r requirements.txt (line 8)) +2026-08-04T12:25:24.4749368Z #14 1.325 Processing /wheels/pytest_cov-7.1.0-py3-none-any.whl (from -r requirements.txt (line 9)) +2026-08-04T12:25:24.4750629Z #14 1.330 Processing /wheels/httpx-0.28.1-py3-none-any.whl (from -r requirements.txt (line 10)) +2026-08-04T12:25:24.4751862Z #14 1.336 Processing /wheels/starlette-1.3.1-py3-none-any.whl (from fastapi==0.141.1->-r requirements.txt (line 1)) +2026-08-04T12:25:24.4753489Z #14 1.342 Processing /wheels/typing_extensions-4.16.0-py3-none-any.whl (from fastapi==0.141.1->-r requirements.txt (line 1)) +2026-08-04T12:25:24.4754942Z #14 1.345 Processing /wheels/typing_inspection-0.4.2-py3-none-any.whl (from fastapi==0.141.1->-r requirements.txt (line 1)) +2026-08-04T12:25:24.4756382Z #14 1.347 Processing /wheels/annotated_doc-0.0.5-py3-none-any.whl (from fastapi==0.141.1->-r requirements.txt (line 1)) +2026-08-04T12:25:24.4757899Z #14 1.351 Processing /wheels/click-8.4.2-py3-none-any.whl (from uvicorn==0.52.1->uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:24.4759478Z #14 1.353 Processing /wheels/h11-0.16.0-py3-none-any.whl (from uvicorn==0.52.1->uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:24.6368102Z #14 1.357 Processing /wheels/greenlet-3.5.4-cp312-cp312-manylinux_2_24_x86_64.manylinux_2_28_x86_64.whl (from SQLAlchemy==2.0.51->-r requirements.txt (line 3)) +2026-08-04T12:25:24.6370667Z #14 1.363 Processing /wheels/annotated_types-0.8.0-py3-none-any.whl (from pydantic==2.13.4->-r requirements.txt (line 6)) +2026-08-04T12:25:24.6372680Z #14 1.366 Processing /wheels/pydantic_core-2.46.4-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (from pydantic==2.13.4->-r requirements.txt (line 6)) +2026-08-04T12:25:24.6374264Z #14 1.372 Processing /wheels/iniconfig-2.3.0-py3-none-any.whl (from pytest==9.1.1->-r requirements.txt (line 8)) +2026-08-04T12:25:24.6375178Z #14 1.374 Processing /wheels/packaging-26.2-py3-none-any.whl (from pytest==9.1.1->-r requirements.txt (line 8)) +2026-08-04T12:25:24.6376059Z #14 1.376 Processing /wheels/pluggy-1.6.0-py3-none-any.whl (from pytest==9.1.1->-r requirements.txt (line 8)) +2026-08-04T12:25:24.6376922Z #14 1.379 Processing /wheels/pygments-2.20.0-py3-none-any.whl (from pytest==9.1.1->-r requirements.txt (line 8)) +2026-08-04T12:25:24.6378139Z #14 1.385 Processing /wheels/coverage-7.15.3-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (from coverage[toml]>=7.10.6->pytest-cov==7.1.0->-r requirements.txt (line 9)) +2026-08-04T12:25:24.6379330Z #14 1.390 Processing /wheels/anyio-4.14.2-py3-none-any.whl (from httpx==0.28.1->-r requirements.txt (line 10)) +2026-08-04T12:25:24.6380194Z #14 1.393 Processing /wheels/certifi-2026.7.22-py3-none-any.whl (from httpx==0.28.1->-r requirements.txt (line 10)) +2026-08-04T12:25:24.6381338Z #14 1.396 Processing /wheels/httpcore-1.0.9-py3-none-any.whl (from httpx==0.28.1->-r requirements.txt (line 10)) +2026-08-04T12:25:24.6382297Z #14 1.399 Processing /wheels/idna-3.18-py3-none-any.whl (from httpx==0.28.1->-r requirements.txt (line 10)) +2026-08-04T12:25:24.6383235Z #14 1.403 Processing /wheels/psycopg_binary-3.3.4-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.whl (from psycopg[binary]==3.3.4->-r requirements.txt (line 4)) +2026-08-04T12:25:24.6384359Z #14 1.412 Processing /wheels/httptools-0.8.0-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:24.6385534Z #14 1.416 Processing /wheels/pyyaml-6.0.3-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:24.6386679Z #14 1.419 Processing /wheels/uvloop-0.22.1-cp312-cp312-manylinux2014_x86_64.manylinux_2_17_x86_64.manylinux_2_28_x86_64.whl (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:24.6389611Z #14 1.427 Processing /wheels/watchfiles-1.2.0-cp312-cp312-manylinux_2_17_x86_64.manylinux2014_x86_64.whl (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:24.6390937Z #14 1.430 Processing /wheels/websockets-17.0.1-cp312-cp312-manylinux1_x86_64.manylinux_2_28_x86_64.manylinux_2_5_x86_64.whl (from uvicorn[standard]==0.52.1->-r requirements.txt (line 2)) +2026-08-04T12:25:24.6393032Z #14 1.516 Installing collected packages: websockets, uvloop, typing-extensions, ruff, pyyaml, python-dotenv, pygments, psycopg-binary, pluggy, packaging, iniconfig, idna, httptools, h11, greenlet, coverage, click, certifi, annotated-types, annotated-doc, uvicorn, typing-inspection, SQLAlchemy, pytest, pydantic-core, psycopg, httpcore, anyio, watchfiles, starlette, pytest-cov, pydantic, httpx, fastapi +2026-08-04T12:25:28.3092848Z #14 5.188 Successfully installed SQLAlchemy-2.0.51 annotated-doc-0.0.5 annotated-types-0.8.0 anyio-4.14.2 certifi-2026.7.22 click-8.4.2 coverage-7.15.3 fastapi-0.141.1 greenlet-3.5.4 h11-0.16.0 httpcore-1.0.9 httptools-0.8.0 httpx-0.28.1 idna-3.18 iniconfig-2.3.0 packaging-26.2 pluggy-1.6.0 psycopg-3.3.4 psycopg-binary-3.3.4 pydantic-2.13.4 pydantic-core-2.46.4 pygments-2.20.0 pytest-9.1.1 pytest-cov-7.1.0 python-dotenv-1.2.2 pyyaml-6.0.3 ruff-0.16.1 starlette-1.3.1 typing-extensions-4.16.0 typing-inspection-0.4.2 uvicorn-0.52.1 uvloop-0.22.1 watchfiles-1.2.0 websockets-17.0.1 +2026-08-04T12:25:28.4530042Z #14 5.189 WARNING: Running pip as the 'root' user can result in broken permissions and conflicting behaviour with the system package manager, possibly rendering your system unusable. It is recommended to use a virtual environment instead: https://pip.pypa.io/warnings/venv. Use the --root-user-action option if you know what you are doing and want to suppress this warning. +2026-08-04T12:25:28.4532496Z #14 DONE 5.3s +2026-08-04T12:25:28.6189099Z +2026-08-04T12:25:28.6190527Z #15 [stage-1 7/7] COPY --chown=appuser:appuser app /app/app +2026-08-04T12:25:28.6191219Z #15 DONE 0.0s +2026-08-04T12:25:28.6192099Z +2026-08-04T12:25:28.6192839Z #16 exporting to image +2026-08-04T12:25:28.6193574Z #16 exporting layers +2026-08-04T12:25:29.4313810Z #16 exporting layers 1.0s done +2026-08-04T12:25:29.4514575Z #16 writing image sha256:30936a48483795ac939a182093399512c7d0b8141ac2fbc804b723b281486914 done +2026-08-04T12:25:29.4515366Z #16 naming to ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2 done +2026-08-04T12:25:29.4515902Z #16 DONE 1.0s +2026-08-04T12:25:29.4629765Z Node 20 is being deprecated. This workflow is running with Node 24 by default. If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ +2026-08-04T12:25:29.4631315Z ##[group]Run docker/login-action@v3 +2026-08-04T12:25:29.4631770Z with: +2026-08-04T12:25:29.4631955Z registry: ghcr.io +2026-08-04T12:25:29.4632510Z username: Ronaldo-F-dev +2026-08-04T12:25:29.4635249Z password: *** +2026-08-04T12:25:29.4635462Z logout: true +2026-08-04T12:25:29.4635658Z ##[endgroup] +2026-08-04T12:25:29.7812151Z Logging into ghcr.io... +2026-08-04T12:25:29.7837592Z (node:2545) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. +2026-08-04T12:25:29.7838466Z (Use `node --trace-deprecation ...` to show where the warning was created) +2026-08-04T12:25:30.3764138Z Login Succeeded! +2026-08-04T12:25:30.3894677Z ##[group]Run docker push "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2" +2026-08-04T12:25:30.3895264Z ^[[36;1mdocker push "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2"^[[0m +2026-08-04T12:25:30.3895644Z ^[[36;1mif [ -n "" ]; then^[[0m +2026-08-04T12:25:30.3895878Z ^[[36;1m docker push ""^[[0m +2026-08-04T12:25:30.3896092Z ^[[36;1mfi^[[0m +2026-08-04T12:25:30.3943095Z shell: /usr/bin/bash -e {0} +2026-08-04T12:25:30.3943345Z ##[endgroup] +2026-08-04T12:25:30.4210605Z The push refers to repository [ghcr.io/ronaldo-f-dev/kps-tasks-api] +2026-08-04T12:25:30.5678723Z 54e75d66762b: Preparing +2026-08-04T12:25:30.5679128Z 1f5044b53525: Preparing +2026-08-04T12:25:30.5679386Z 087df81f90ef: Preparing +2026-08-04T12:25:30.5679632Z af62f61332f0: Preparing +2026-08-04T12:25:30.5679987Z 9091a87b41d6: Preparing +2026-08-04T12:25:30.5680366Z f4fa0d5a8f5b: Preparing +2026-08-04T12:25:30.5680740Z b80f3ed1ee6d: Preparing +2026-08-04T12:25:30.5681000Z 83fdf57f71f2: Preparing +2026-08-04T12:25:30.5681424Z ccbaccfc0388: Preparing +2026-08-04T12:25:30.5681717Z f2ec4de84f55: Preparing +2026-08-04T12:25:30.5685437Z f4fa0d5a8f5b: Waiting +2026-08-04T12:25:30.5686169Z 83fdf57f71f2: Waiting +2026-08-04T12:25:30.5686743Z b80f3ed1ee6d: Waiting +2026-08-04T12:25:30.5687102Z ccbaccfc0388: Waiting +2026-08-04T12:25:32.4916062Z f2ec4de84f55: Waiting +2026-08-04T12:25:32.4916401Z 54e75d66762b: Pushed +2026-08-04T12:25:32.5340290Z af62f61332f0: Pushed +2026-08-04T12:25:32.7604466Z b80f3ed1ee6d: Layer already exists +2026-08-04T12:25:32.7809723Z 9091a87b41d6: Pushed +2026-08-04T12:25:32.9368755Z 83fdf57f71f2: Layer already exists +2026-08-04T12:25:32.9431137Z ccbaccfc0388: Layer already exists +2026-08-04T12:25:33.0925661Z f2ec4de84f55: Layer already exists +2026-08-04T12:25:33.9166133Z 087df81f90ef: Pushed +2026-08-04T12:25:34.2292350Z f4fa0d5a8f5b: Pushed +2026-08-04T12:26:37.8976300Z 1f5044b53525: Pushed +2026-08-04T12:26:40.3733568Z commit-67e62a2: digest: sha256:13115ebd1e199234a124cae3679d219bbaed90aa462949bc10aeff106c983b38 size: 2412 +2026-08-04T12:26:40.3787299Z ##[group]Run docker manifest inspect "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2" > /dev/null +2026-08-04T12:26:40.3788043Z ^[[36;1mdocker manifest inspect "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2" > /dev/null^[[0m +2026-08-04T12:26:40.3788638Z ^[[36;1mecho "Image available: ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2"^[[0m +2026-08-04T12:26:40.3831853Z shell: /usr/bin/bash -e {0} +2026-08-04T12:26:40.3832393Z ##[endgroup] +2026-08-04T12:26:41.1581502Z Image available: ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2 +2026-08-04T12:26:41.1655399Z Node 20 is being deprecated. This workflow is running with Node 24 by default. If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ +2026-08-04T12:26:41.1656652Z Post job cleanup. +2026-08-04T12:26:41.4765491Z ##[group]Logout from ghcr.io +2026-08-04T12:26:41.4788206Z (node:2628) [DEP0040] DeprecationWarning: The `punycode` module is deprecated. Please use a userland alternative instead. +2026-08-04T12:26:41.4789085Z (Use `node --trace-deprecation ...` to show where the warning was created) +2026-08-04T12:26:41.4821747Z [command]/usr/bin/docker logout ghcr.io +2026-08-04T12:26:41.4970372Z Removing login credentials for ghcr.io +2026-08-04T12:26:41.5000396Z ##[endgroup] +2026-08-04T12:26:41.5001263Z ##[group]Post cache +2026-08-04T12:26:41.5002697Z State not set +2026-08-04T12:26:41.5003833Z ##[endgroup] +2026-08-04T12:26:41.5238693Z Node 20 is being deprecated. This workflow is running with Node 24 by default. If you need to temporarily use Node 20, you can set the ACTIONS_ALLOW_USE_UNSECURE_NODE_VERSION=true environment variable. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ +2026-08-04T12:26:41.5240438Z Post job cleanup. +2026-08-04T12:26:41.6109998Z [command]/usr/bin/git version +2026-08-04T12:26:41.6151630Z git version 2.54.0 +2026-08-04T12:26:41.6207455Z Temporarily overriding HOME='/home/runner/work/_temp/3a4c36ce-48bb-45e2-a77d-9129533e3a1a' before making global git config changes +2026-08-04T12:26:41.6208905Z Adding repository directory to the temporary git global config as a safe directory +2026-08-04T12:26:41.6214094Z [command]/usr/bin/git config --global --add safe.directory /home/runner/work/devops-prj3/devops-prj3 +2026-08-04T12:26:41.6254446Z [command]/usr/bin/git config --local --name-only --get-regexp core\.sshCommand +2026-08-04T12:26:41.6292531Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'core\.sshCommand' && git config --local --unset-all 'core.sshCommand' || :" +2026-08-04T12:26:41.6546036Z [command]/usr/bin/git config --local --name-only --get-regexp http\.https\:\/\/github\.com\/\.extraheader +2026-08-04T12:26:41.6577253Z http.https://github.com/.extraheader +2026-08-04T12:26:41.6590198Z [command]/usr/bin/git config --local --unset-all http.https://github.com/.extraheader +2026-08-04T12:26:41.6624100Z [command]/usr/bin/git submodule foreach --recursive sh -c "git config --local --name-only --get-regexp 'http\.https\:\/\/github\.com\/\.extraheader' && git config --local --unset-all 'http.https://github.com/.extraheader' || :" +2026-08-04T12:26:41.6916827Z [command]/usr/bin/git config --local --name-only --get-regexp ^includeIf\.gitdir: +2026-08-04T12:26:41.6957382Z [command]/usr/bin/git submodule foreach --recursive git config --local --show-origin --name-only --get-regexp remote.origin.url +2026-08-04T12:26:41.7340968Z Cleaning up orphan processes +2026-08-04T12:26:41.7790826Z ##[warning]Node.js 20 is deprecated. The following actions target Node.js 20 but are being forced to run on Node.js 24: actions/checkout@v4, docker/login-action@v3. For more information see: https://github.blog/changelog/2025-09-19-deprecation-of-node-20-on-github-actions-runners/ diff --git a/evidence/registry-push.txt b/evidence/registry-push.txt new file mode 100644 index 0000000..f20c5a6 --- /dev/null +++ b/evidence/registry-push.txt @@ -0,0 +1,40 @@ +2026-08-04T12:25:30.3894677Z ##[group]Run docker push "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2" +2026-08-04T12:25:30.3895264Z ^[[36;1mdocker push "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2"^[[0m +2026-08-04T12:25:30.3895644Z ^[[36;1mif [ -n "" ]; then^[[0m +2026-08-04T12:25:30.3895878Z ^[[36;1m docker push ""^[[0m +2026-08-04T12:25:30.3896092Z ^[[36;1mfi^[[0m +2026-08-04T12:25:30.3943095Z shell: /usr/bin/bash -e {0} +2026-08-04T12:25:30.3943345Z ##[endgroup] +2026-08-04T12:25:30.4210605Z The push refers to repository [ghcr.io/ronaldo-f-dev/kps-tasks-api] +2026-08-04T12:25:30.5678723Z 54e75d66762b: Preparing +2026-08-04T12:25:30.5679128Z 1f5044b53525: Preparing +2026-08-04T12:25:30.5679386Z 087df81f90ef: Preparing +2026-08-04T12:25:30.5679632Z af62f61332f0: Preparing +2026-08-04T12:25:30.5679987Z 9091a87b41d6: Preparing +2026-08-04T12:25:30.5680366Z f4fa0d5a8f5b: Preparing +2026-08-04T12:25:30.5680740Z b80f3ed1ee6d: Preparing +2026-08-04T12:25:30.5681000Z 83fdf57f71f2: Preparing +2026-08-04T12:25:30.5681424Z ccbaccfc0388: Preparing +2026-08-04T12:25:30.5681717Z f2ec4de84f55: Preparing +2026-08-04T12:25:30.5685437Z f4fa0d5a8f5b: Waiting +2026-08-04T12:25:30.5686169Z 83fdf57f71f2: Waiting +2026-08-04T12:25:30.5686743Z b80f3ed1ee6d: Waiting +2026-08-04T12:25:30.5687102Z ccbaccfc0388: Waiting +2026-08-04T12:25:32.4916062Z f2ec4de84f55: Waiting +2026-08-04T12:25:32.4916401Z 54e75d66762b: Pushed +2026-08-04T12:25:32.5340290Z af62f61332f0: Pushed +2026-08-04T12:25:32.7604466Z b80f3ed1ee6d: Layer already exists +2026-08-04T12:25:32.7809723Z 9091a87b41d6: Pushed +2026-08-04T12:25:32.9368755Z 83fdf57f71f2: Layer already exists +2026-08-04T12:25:32.9431137Z ccbaccfc0388: Layer already exists +2026-08-04T12:25:33.0925661Z f2ec4de84f55: Layer already exists +2026-08-04T12:25:33.9166133Z 087df81f90ef: Pushed +2026-08-04T12:25:34.2292350Z f4fa0d5a8f5b: Pushed +2026-08-04T12:26:37.8976300Z 1f5044b53525: Pushed +2026-08-04T12:26:40.3733568Z commit-67e62a2: digest: sha256:13115ebd1e199234a124cae3679d219bbaed90aa462949bc10aeff106c983b38 size: 2412 +2026-08-04T12:26:40.3787299Z ##[group]Run docker manifest inspect "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2" > /dev/null +2026-08-04T12:26:40.3788043Z ^[[36;1mdocker manifest inspect "ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2" > /dev/null^[[0m +2026-08-04T12:26:40.3788638Z ^[[36;1mecho "Image available: ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2"^[[0m +2026-08-04T12:26:40.3831853Z shell: /usr/bin/bash -e {0} +2026-08-04T12:26:40.3832393Z ##[endgroup] +2026-08-04T12:26:41.1581502Z Image available: ghcr.io/ronaldo-f-dev/kps-tasks-api:commit-67e62a2 From 45e1dbab1b5bdd94e8189f24fb1457b9dff8d532 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Tue, 4 Aug 2026 13:28:41 +0100 Subject: [PATCH 09/11] docs(prj4): reference failing build test as proof for Day 2 task 20 --- docs/prj4/image-versioning.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/prj4/image-versioning.md b/docs/prj4/image-versioning.md index 4c7443d..de5509e 100644 --- a/docs/prj4/image-versioning.md +++ b/docs/prj4/image-versioning.md @@ -47,3 +47,12 @@ Extrait de `.github/workflows/ci.yml` (job `docker_build`) : - Un push normal sur une branche → une seule image, taguée par son commit. - Un `git tag v1.0.1 && git push origin v1.0.1` → le pipeline se redéclenche (le tag Git fait partie des déclencheurs), et l'image obtient **en plus** le tag `v1.0.1`, pointant vers exactement le même contenu que le tag commit correspondant. - Le déploiement en production (Jour 3) utilisera toujours le tag de version, jamais le tag de commit ni `latest`. + +## Test d'un build en échec, puis correction (tâche 20) + +Une erreur de chemin a été introduite volontairement dans le `Dockerfile` (`COPY --chown=appuser:appuser app-typo /app/app`, un répertoire qui n'existe pas) pour vérifier que le job `docker_build` échoue proprement et de façon lisible. + +- Run en échec : le build s'arrête avec `"/app-typo": not found`, log complet dans [evidence/image-build-failed.txt](../../evidence/image-build-failed.txt) +- Correctif appliqué (chemin restauré), run suivant entièrement vert : build, tag, login, push, vérification — log complet dans [evidence/image-build.txt](../../evidence/image-build.txt) et [evidence/registry-push.txt](../../evidence/registry-push.txt) + +Ce test confirme aussi une propriété importante du pipeline : quand le `docker build` échoue, les étapes suivantes (login, push, vérification) ne s'exécutent pas — aucune image cassée ne risque d'être poussée vers le registre. From c6e49ce437a4c5003c392e4b82e2e7f16b425e2b Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Thu, 6 Aug 2026 08:39:30 +0100 Subject: [PATCH 10/11] docs(prj4): document Day 1 VPS prep (tasks 5-9) and discovery of existing manual deployment --- docker-compose.prod.yml | 52 ++++++++++++++++++++++++++ docs/prj4/cicd-architecture.md | 67 +++++++++++++++++++++++++++++++--- 2 files changed, 114 insertions(+), 5 deletions(-) create mode 100644 docker-compose.prod.yml diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 0000000..24ed1d5 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,52 @@ +services: + db: + image: postgres:16-alpine + restart: unless-stopped + environment: + POSTGRES_DB: ${POSTGRES_DB} + POSTGRES_USER: ${POSTGRES_USER} + POSTGRES_PASSWORD: ${POSTGRES_PASSWORD} + volumes: + - postgres_data:/var/lib/postgresql/data + networks: + - kps_net + healthcheck: + test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"] + interval: 5s + timeout: 5s + retries: 10 + start_period: 10s + + app: + image: ${IMAGE_TAG} + restart: unless-stopped + environment: + APP_NAME: ${APP_NAME} + APP_ENV: ${APP_ENV} + APP_VERSION: ${APP_VERSION} + LOG_LEVEL: ${LOG_LEVEL} + DATABASE_URL: ${DATABASE_URL} + depends_on: + db: + condition: service_healthy + ports: + - "${APP_PORT}:8000" + networks: + - kps_net + healthcheck: + test: + - CMD-SHELL + - python -c "from urllib.request import urlopen; urlopen('http://127.0.0.1:8000/health').read()" + interval: 10s + timeout: 5s + retries: 5 + start_period: 20s + command: > + sh -c "python -m app.init_db && exec uvicorn app.main:app --host 0.0.0.0 --port 8000" + +volumes: + postgres_data: + +networks: + kps_net: + driver: bridge diff --git a/docs/prj4/cicd-architecture.md b/docs/prj4/cicd-architecture.md index d41a9ad..069815e 100644 --- a/docs/prj4/cicd-architecture.md +++ b/docs/prj4/cicd-architecture.md @@ -80,9 +80,66 @@ Ces trois valeurs sont maintenant enregistrées dans *Settings → Secrets and v Ces variables serviront au Jour 2 (le job de la CI qui pousse l'image) et au Jour 3 (le script `deploy.sh` qui, exécuté sur ou vers le VPS, doit lui aussi s'authentifier pour faire le `pull`). -## Suite (tâches 4 et +) +## 4. Utilisateur de déploiement sur le VPS (tâche 5) -- Tâche 4 : liste complète des variables CI/CD nécessaires (couvert en partie ci-dessus, à compléter dans `docs/prj4/ci-cd-variables.md`) -- Tâche 5-7 : utilisateur de déploiement sur le VPS, accès SSH depuis le pipeline, répertoire applicatif -- Tâche 8-9 : schéma d'architecture de déploiement complet -- Tâche 10 : vérifier que le VPS peut lui-même faire un `pull` (avec le PAT stocké) +**Décision : réutiliser l'utilisateur existant `ronaldo`, ne pas en créer un nouveau.** + +Pourquoi : +- Il est déjà **non-root** — ce qui est justement une des contraintes non négociables du brief ("non-root deployment user"). Créer un utilisateur dédié supplémentaire n'apporterait rien de plus en sécurité ici, juste une identité de plus à gérer. +- Il a déjà accès SSH au VPS (clé régénérée le 2026-08-03, suite à l'incident de sécurité documenté dans `docs/prj3/security-and-quality.md`). +- Il fait déjà partie du groupe `sudo` pour les opérations d'administration ponctuelles (créer un dossier, changer un groupe) — mais **pas** pour les déploiements de routine, qui doivent pouvoir s'exécuter sans mot de passe interactif (voir point suivant). + +Un ajustement a été nécessaire : `ronaldo` n'était pas membre du groupe `docker`, donc chaque commande `docker`/`docker compose` exigeait `sudo` — impossible à automatiser depuis un pipeline (sudo demande un mot de passe interactif, qu'un job CI ne peut pas fournir). Correctif, exécuté une fois manuellement sur le VPS (nécessite les droits root, donc pas automatisable depuis ce poste) : + +```bash +sudo usermod -aG docker ronaldo +``` + +Une fois dans le groupe `docker`, `ronaldo` peut lancer `docker`/`docker compose` sans `sudo` — exactement ce qu'il faut pour un déploiement automatisé non interactif. + +## 5. Accès SSH depuis le pipeline (tâche 6) + +La clé SSH déjà présente (`~/.ssh/id_ed25519`, régénérée le 2026-08-03) a été testée avec succès : + +```bash +ssh -i ~/.ssh/id_ed25519 ronaldo@ "whoami" +# → ronaldo +``` + +Pour que le pipeline GitHub Actions puisse se connecter de la même façon, la **clé privée** doit devenir un secret (`DEPLOY_SSH_PRIVATE_KEY`), et l'hôte/l'utilisateur des secrets simples (`DEPLOY_HOST`, `DEPLOY_USER`) — voir `docs/prj4/ci-cd-variables.md`. + +## 6. Répertoire applicatif sur le VPS (tâche 7) + +Créé (opération root, faite une fois manuellement) : + +```bash +sudo mkdir -p /opt/kps-tasks-api +sudo chown ronaldo:ronaldo /opt/kps-tasks-api +``` + +`ronaldo` est propriétaire du dossier, donc tout ce qui suit (copier `docker-compose.prod.yml`, écrire `.env`, exécuter `deploy.sh`) peut se faire sans `sudo`. + +## 7. Découverte importante : un déploiement manuel existe déjà (tâches 8-9) + +En inspectant le VPS, on a trouvé que le déploiement manuel du **Projet 2** tourne toujours, dans `/home/ronaldo/app/` — exactement le scénario "déploiement manuel" que le brief du Projet 4 décrit comme point de départ à corriger. Deux conséquences concrètes pour la suite : + +1. **Conflit de port** : cet ancien déploiement utilise aussi le port 8000. Le nouveau déploiement automatisé (`/opt/kps-tasks-api/`) va le remplacer, pas coexister avec lui. +2. **Données réelles à préserver** : la base PostgreSQL de cet ancien déploiement contient de vraies données, dans un volume Docker nommé `app_postgres_data` (le nom vient du dossier `app/`, que Docker Compose utilise par défaut comme préfixe). Le nouveau `docker-compose.prod.yml`, lancé depuis `/opt/kps-tasks-api/`, nommerait son volume différemment (`kps-tasks-api_postgres_data`) s'il n'était pas configuré explicitement — ce qui créerait une base **vide**, et donnerait l'impression d'une perte de données alors que l'ancien volume existerait toujours, juste orphelin. La correction : déclarer le volume comme **externe**, avec le nom exact de l'existant, dans `docker-compose.prod.yml`, pour que le nouveau déploiement reprenne exactement les mêmes données (détail dans `docs/prj4/deployment-process.md`). + +Schéma d'architecture complet (flux cible) : + +``` +Dépôt GitHub (push/tag) + → CI : lint, test, build, Gitleaks, Sonar + → docker_build : build + tag + push vers GHCR + → deploy (nouveau job) : + SSH vers le VPS (ronaldo@, clé privée en secret) + → copie docker-compose.prod.yml + scripts/deploy.sh vers /opt/kps-tasks-api/ + → exécution de deploy.sh sur le VPS : + docker login (registre) → docker compose pull → docker compose up -d + → vérification /health +``` + +## 8. Vérifier que le VPS peut faire un `pull` (tâche 10) + +À valider une fois `docker-compose.prod.yml` et le job `deploy` en place (Jour 3, section suivante) — le premier déploiement réel servira de preuve. From fdcb3500b639ef72e39359c9b4c4912d7923e993 Mon Sep 17 00:00:00 2001 From: Ronaldo Date: Thu, 6 Aug 2026 08:46:12 +0100 Subject: [PATCH 11/11] ci: add automated deployment job to VPS with manual approval gate --- .github/workflows/ci.yml | 40 ++++++++++++++++++++++ docker-compose.prod.yml | 2 ++ scripts/deploy.sh | 73 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 115 insertions(+) create mode 100755 scripts/deploy.sh diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index f1d9621..fa989bd 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -57,6 +57,9 @@ jobs: contents: read packages: write + outputs: + commit_tag: ${{ steps.tags.outputs.commit_tag }} + steps: - uses: actions/checkout@v4 @@ -100,6 +103,43 @@ jobs: docker manifest inspect "${{ steps.tags.outputs.commit_tag }}" > /dev/null echo "Image available: ${{ steps.tags.outputs.commit_tag }}" + deploy: + name: Deploy to VPS + runs-on: ubuntu-latest + + needs: docker_build + + if: github.ref == 'refs/heads/main' + + environment: production + + steps: + - uses: actions/checkout@v4 + + - name: Set up SSH + run: | + mkdir -p ~/.ssh + printf '%s\n' "${{ secrets.DEPLOY_SSH_PRIVATE_KEY }}" > ~/.ssh/deploy_key + chmod 600 ~/.ssh/deploy_key + ssh-keyscan -H "${{ secrets.DEPLOY_HOST }}" >> ~/.ssh/known_hosts 2>/dev/null + + - name: Copy deployment files + run: | + scp -i ~/.ssh/deploy_key docker-compose.prod.yml scripts/deploy.sh \ + "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}:/opt/kps-tasks-api/" + + - name: Run deployment + run: | + ssh -i ~/.ssh/deploy_key "${{ secrets.DEPLOY_USER }}@${{ secrets.DEPLOY_HOST }}" " + cd /opt/kps-tasks-api && + chmod +x deploy.sh && + IMAGE_TAG='${{ needs.docker_build.outputs.commit_tag }}' \ + REGISTRY_URL='${{ secrets.REGISTRY_URL }}' \ + REGISTRY_USER='${{ secrets.REGISTRY_USER }}' \ + REGISTRY_PASSWORD='${{ secrets.REGISTRY_PASSWORD }}' \ + ./deploy.sh + " + secret_scan: name: Gitleaks secret scan runs-on: ubuntu-latest diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index 24ed1d5..81ea127 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -46,6 +46,8 @@ services: volumes: postgres_data: + external: true + name: ${POSTGRES_VOLUME_NAME:-app_postgres_data} networks: kps_net: diff --git a/scripts/deploy.sh b/scripts/deploy.sh new file mode 100755 index 0000000..b6ec7b6 --- /dev/null +++ b/scripts/deploy.sh @@ -0,0 +1,73 @@ +#!/usr/bin/env sh +set -eu + +# Deploys a given image tag to the app directory on the VPS. +# Meant to run ON THE VPS (in the application directory, e.g. /opt/kps-tasks-api), +# with docker-compose.prod.yml and .env already present. +# +# Required environment variables: +# IMAGE_TAG full image reference to deploy (e.g. ghcr.io/owner/kps-tasks-api:v1.0.0) +# REGISTRY_URL e.g. ghcr.io +# REGISTRY_USER +# REGISTRY_PASSWORD +# +# Optional: +# APP_DIR defaults to the current directory +# HEALTH_RETRIES defaults to 10 +# HEALTH_DELAY defaults to 3 (seconds) + +IMAGE_TAG=${IMAGE_TAG:?IMAGE_TAG is required} +REGISTRY_URL=${REGISTRY_URL:?REGISTRY_URL is required} +REGISTRY_USER=${REGISTRY_USER:?REGISTRY_USER is required} +REGISTRY_PASSWORD=${REGISTRY_PASSWORD:?REGISTRY_PASSWORD is required} + +APP_DIR=${APP_DIR:-$(pwd)} +HEALTH_RETRIES=${HEALTH_RETRIES:-10} +HEALTH_DELAY=${HEALTH_DELAY:-3} + +cd "$APP_DIR" + +echo "==> Deploying $IMAGE_TAG in $APP_DIR" + +if [ -f current-version.txt ]; then + cp current-version.txt previous-version.txt + echo "==> Previous version saved: $(cat previous-version.txt)" +else + echo "==> No previous version on record (first deployment)" +fi + +if grep -q '^IMAGE_TAG=' .env 2>/dev/null; then + sed -i "s#^IMAGE_TAG=.*#IMAGE_TAG=$IMAGE_TAG#" .env +else + echo "IMAGE_TAG=$IMAGE_TAG" >> .env +fi + +echo "==> Logging in to $REGISTRY_URL" +echo "$REGISTRY_PASSWORD" | docker login "$REGISTRY_URL" -u "$REGISTRY_USER" --password-stdin + +echo "==> Pulling new image" +docker compose -f docker-compose.prod.yml --env-file .env pull + +echo "==> Restarting application" +docker compose -f docker-compose.prod.yml --env-file .env up -d + +docker logout "$REGISTRY_URL" > /dev/null 2>&1 || true + +APP_PORT=$(grep '^APP_PORT=' .env | cut -d= -f2) +APP_PORT=${APP_PORT:-8000} + +echo "==> Waiting for the application to respond on port $APP_PORT" +i=1 +while [ "$i" -le "$HEALTH_RETRIES" ]; do + if curl -fsS "http://127.0.0.1:${APP_PORT}/health" > /dev/null 2>&1; then + echo "==> Application responded, deployment successful" + echo "$IMAGE_TAG" > current-version.txt + exit 0 + fi + echo "==> Attempt $i/$HEALTH_RETRIES: not ready yet, waiting ${HEALTH_DELAY}s" + i=$((i + 1)) + sleep "$HEALTH_DELAY" +done + +echo "==> Application did not respond after $HEALTH_RETRIES attempts" >&2 +exit 1