From 4afe4be5e505c7ba150b56215711380d8c328640 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Fri, 28 Nov 2025 19:38:05 -0800 Subject: [PATCH 1/9] Add a workflow to build and test Docker images --- .github/workflows/docker.yml | 167 ++++++++++++++++++++++++++++++++++- 1 file changed, 163 insertions(+), 4 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 22a86db56..52f893bb9 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -18,10 +18,10 @@ permissions: jobs: build: - # Run on tag pushes OR on PRs that have the "docker" label + # Run on tag pushes OR on PRs that have the "docker" or "docker-test" label if: | github.event_name == 'push' || - (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker')) + (github.event_name == 'pull_request' && (contains(github.event.pull_request.labels.*.name, 'docker') || contains(github.event.pull_request.labels.*.name, 'docker-test'))) strategy: matrix: service: @@ -33,6 +33,7 @@ jobs: outputs: image-digest-frontend: ${{ matrix.service == 'frontend' && steps.docker-build.outputs.digest || '' }} image-digest-backend: ${{ matrix.service == 'backend' && steps.docker-build.outputs.digest || '' }} + tag: ${{ matrix.service == 'frontend' && (steps.set-tag-push.outputs.tag || steps.set-tag-pr.outputs.tag) || '' }} steps: - name: Replace the current swap file shell: bash @@ -65,7 +66,11 @@ jobs: # Only for tag pushes: use the Git tag as TAG - name: Set TAG from pushed tag if: github.event_name == 'push' - run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + id: set-tag-push + run: | + TAG="${GITHUB_REF/refs\/tags\//}" + echo "TAG=${TAG}" >> $GITHUB_ENV + echo "tag=${TAG}" >> $GITHUB_OUTPUT - name: Add SHORT_SHA env property with commit short sha run: | @@ -86,13 +91,16 @@ jobs: # For PRs: use package.json version + short sha as TAG - name: Set TAG from service package.json for pull requests if: github.event_name == 'pull_request' + id: set-tag-pr run: | if [ "${{ matrix.service }}" = "frontend" ]; then VERSION=$(jq -r '.version' frontend/package.json) else VERSION=$(jq -r '.version' backend/package.json) fi - echo "TAG=v${VERSION}-${SHORT_SHA}" >> $GITHUB_ENV + TAG="v${VERSION}-${SHORT_SHA}" + echo "TAG=${TAG}" >> $GITHUB_ENV + echo "tag=${TAG}" >> $GITHUB_OUTPUT - name: Show set environment variables run: | @@ -178,3 +186,154 @@ jobs: docker buildx imagetools create \ --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \ ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG + + test-images: + needs: build + # Only run for PRs with "docker-test" label + if: ${{ needs.build.result == 'success' && github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker-test') }} + runs-on: ubuntu-latest + timeout-minutes: 30 + name: Test built Docker images + steps: + - name: Checkout project + uses: actions/checkout@v4 + + - name: Add SHORT_SHA env property with commit short sha + run: | + SHA="${{ github.event.pull_request.head.sha }}" + echo "SHORT_SHA=${SHA:0:8}" >> $GITHUB_ENV + + - name: Set TAG from frontend package.json + run: | + VERSION=$(jq -r '.version' frontend/package.json) + echo "TAG=v${VERSION}-${SHORT_SHA}" >> $GITHUB_ENV + + - name: Show set environment variables + run: | + printf " TAG: %s\n" "$TAG" + printf " SHORT_SHA: %s\n" "$SHORT_SHA" + + - name: Init repo for Dockerization + run: docker/init.sh "$TAG" + + - name: Set up Docker Buildx + uses: docker/setup-buildx-action@v3 + + - name: Build frontend image locally + run: | + docker buildx build \ + --tag test-frontend:$TAG \ + --build-arg commitHash=$SHORT_SHA \ + --load \ + --platform linux/amd64 \ + ./frontend/ + + - name: Build backend image locally + run: | + docker buildx build \ + --tag test-backend:$TAG \ + --build-context rustgbt=./rust \ + --build-context backend=./backend \ + --build-arg commitHash=$SHORT_SHA \ + --load \ + --platform linux/amd64 \ + ./backend/ + + - name: Generate docker-compose test file + run: | + cat > docker-compose.test.yml </dev/null; then + echo "Database is ready!" + break + fi + sleep 2 + elapsed=$((elapsed + 2)) + done + if [ $elapsed -ge $timeout ]; then + echo "Database did not become ready in time" + docker compose -f docker-compose.test.yml logs + exit 1 + fi + + - name: Verify containers are running + run: | + echo "Checking container status..." + docker compose -f docker-compose.test.yml ps + if ! docker compose -f docker-compose.test.yml ps | grep -q "Up"; then + echo "Some containers are not running" + docker compose -f docker-compose.test.yml logs + exit 1 + fi + echo "All containers are running successfully!" + + - name: Show container logs + if: failure() + run: | + docker compose -f docker-compose.test.yml logs + + - name: Clean up containers + if: always() + run: | + docker compose -f docker-compose.test.yml down -v From 1205d1592069c4b966fa0a61ed3b3f2bca51f8c3 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Fri, 28 Nov 2025 20:26:46 -0800 Subject: [PATCH 2/9] Login only if needed --- .github/workflows/docker.yml | 45 ++++++++++++++++++++++++++++-------- 1 file changed, 35 insertions(+), 10 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 52f893bb9..50eee9d35 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -83,6 +83,9 @@ jobs: - name: Login to Docker for building + if: | + github.event_name == 'push' || + (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'docker-test')) run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - name: Checkout project @@ -139,16 +142,38 @@ jobs: - name: Run Docker buildx for ${{ matrix.service }} against tag id: docker-build run: | - docker buildx build \ - --cache-from "type=local,src=/tmp/.buildx-cache" \ - --cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \ - --platform linux/amd64,linux/arm64 \ - --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ - --build-context rustgbt=./rust \ - --build-context backend=./backend \ - --output "type=registry,push=true" \ - --build-arg commitHash=$SHORT_SHA \ - ./${{ matrix.service }}/ + # For docker-test label PRs, build locally without pushing + IS_DOCKER_TEST=false + if [ "${{ github.event_name }}" = "pull_request" ]; then + LABELS="${{ join(github.event.pull_request.labels.*.name, ' ') }}" + if echo "$LABELS" | grep -q "docker-test"; then + IS_DOCKER_TEST=true + fi + fi + + if [ "$IS_DOCKER_TEST" = "true" ]; then + docker buildx build \ + --cache-from "type=local,src=/tmp/.buildx-cache" \ + --cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \ + --platform linux/amd64,linux/arm64 \ + --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ + --build-context rustgbt=./rust \ + --build-context backend=./backend \ + --output "type=image,push=false" \ + --build-arg commitHash=$SHORT_SHA \ + ./${{ matrix.service }}/ + else + docker buildx build \ + --cache-from "type=local,src=/tmp/.buildx-cache" \ + --cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \ + --platform linux/amd64,linux/arm64 \ + --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ + --build-context rustgbt=./rust \ + --build-context backend=./backend \ + --output "type=registry,push=true" \ + --build-arg commitHash=$SHORT_SHA \ + ./${{ matrix.service }}/ + fi tag-latest: needs: build From f3311382e4cebcd08b9ba46f2b995ade6fcd3f44 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Fri, 28 Nov 2025 22:04:45 -0800 Subject: [PATCH 3/9] Skip building with tags --- .github/workflows/docker.yml | 46 +++++++++++------------------------- 1 file changed, 14 insertions(+), 32 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 50eee9d35..2e5a7a2aa 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -141,39 +141,21 @@ jobs: - name: Run Docker buildx for ${{ matrix.service }} against tag id: docker-build + # Skip building/pushing images when this is a docker-test label PR + if: | + github.event_name == 'push' || + (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'docker-test')) run: | - # For docker-test label PRs, build locally without pushing - IS_DOCKER_TEST=false - if [ "${{ github.event_name }}" = "pull_request" ]; then - LABELS="${{ join(github.event.pull_request.labels.*.name, ' ') }}" - if echo "$LABELS" | grep -q "docker-test"; then - IS_DOCKER_TEST=true - fi - fi - - if [ "$IS_DOCKER_TEST" = "true" ]; then - docker buildx build \ - --cache-from "type=local,src=/tmp/.buildx-cache" \ - --cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \ - --platform linux/amd64,linux/arm64 \ - --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ - --build-context rustgbt=./rust \ - --build-context backend=./backend \ - --output "type=image,push=false" \ - --build-arg commitHash=$SHORT_SHA \ - ./${{ matrix.service }}/ - else - docker buildx build \ - --cache-from "type=local,src=/tmp/.buildx-cache" \ - --cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \ - --platform linux/amd64,linux/arm64 \ - --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ - --build-context rustgbt=./rust \ - --build-context backend=./backend \ - --output "type=registry,push=true" \ - --build-arg commitHash=$SHORT_SHA \ - ./${{ matrix.service }}/ - fi + docker buildx build \ + --cache-from "type=local,src=/tmp/.buildx-cache" \ + --cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \ + --platform linux/amd64,linux/arm64 \ + --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ + --build-context rustgbt=./rust \ + --build-context backend=./backend \ + --output "type=registry,push=true" \ + --build-arg commitHash=$SHORT_SHA \ + ./${{ matrix.service }}/ tag-latest: needs: build From f1d831b8dd2dcb5cfec00ca218c8ff242d559836 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Fri, 28 Nov 2025 22:13:50 -0800 Subject: [PATCH 4/9] Fix empty prefix --- .github/workflows/docker.yml | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 2e5a7a2aa..c75b16491 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -18,10 +18,10 @@ permissions: jobs: build: - # Run on tag pushes OR on PRs that have the "docker" or "docker-test" label + # Run on tag pushes OR on PRs that have the "docker" label (but not "docker-test") if: | github.event_name == 'push' || - (github.event_name == 'pull_request' && (contains(github.event.pull_request.labels.*.name, 'docker') || contains(github.event.pull_request.labels.*.name, 'docker-test'))) + (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker') && !contains(github.event.pull_request.labels.*.name, 'docker-test')) strategy: matrix: service: @@ -83,9 +83,6 @@ jobs: - name: Login to Docker for building - if: | - github.event_name == 'push' || - (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'docker-test')) run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - name: Checkout project @@ -141,10 +138,6 @@ jobs: - name: Run Docker buildx for ${{ matrix.service }} against tag id: docker-build - # Skip building/pushing images when this is a docker-test label PR - if: | - github.event_name == 'push' || - (github.event_name == 'pull_request' && !contains(github.event.pull_request.labels.*.name, 'docker-test')) run: | docker buildx build \ --cache-from "type=local,src=/tmp/.buildx-cache" \ @@ -195,9 +188,8 @@ jobs: ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG test-images: - needs: build - # Only run for PRs with "docker-test" label - if: ${{ needs.build.result == 'success' && github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker-test') }} + # Only run for PRs with "docker-test" label (independent of build job) + if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker-test') }} runs-on: ubuntu-latest timeout-minutes: 30 name: Test built Docker images From 893d0deb1048a1d1de14283e832a71359feed1a3 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Fri, 28 Nov 2025 22:22:36 -0800 Subject: [PATCH 5/9] Change database user --- .github/workflows/docker.yml | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c75b16491..18f95b340 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -285,11 +285,15 @@ jobs: MYSQL_PASSWORD: "mempool" MYSQL_ROOT_PASSWORD: "admin" image: mariadb:10.5.21 - user: "1000:1000" restart: on-failure stop_grace_period: 1m tmpfs: - /var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "mempool", "-pmempool"] + interval: 5s + timeout: 5s + retries: 10 EOF cat docker-compose.test.yml From f7a27174f8608c221cc37718b9ef60a55467dd26 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sat, 29 Nov 2025 18:42:37 -0800 Subject: [PATCH 6/9] Reuse example docker-compose file --- .github/workflows/docker.yml | 119 ++++++++++++++++++----------------- 1 file changed, 63 insertions(+), 56 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 18f95b340..c1aeb6d3c 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -238,63 +238,70 @@ jobs: --platform linux/amd64 \ ./backend/ - - name: Generate docker-compose test file + - name: Prepare docker-compose test file run: | - cat > docker-compose.test.yml < /tmp/modify_compose.py << 'SCRIPT_END' + import re + import os + import sys + + # Read the base docker-compose file + with open('docker/docker-compose.yml', 'r') as f: + content = f.read() + + # Get TAG from environment + tag = os.environ.get('TAG', '') + + # Replace image names with locally built test images + content = content.replace('image: mempool/frontend:latest', f'image: test-frontend:{tag}') + content = content.replace('image: mempool/backend:latest', f'image: test-backend:{tag}') + + # Change web port mapping from 80:8080 to 8080:8080 + content = content.replace('- 80:8080', '- 8080:8080') + + # Remove volumes from api service + content = re.sub(r' volumes:\n - \.\/data:\/backend\/cache\n', '', content) + + # For db service: remove user and volumes, add tmpfs and healthcheck + # Remove user line from db service (only the one in db service) + lines = content.split('\n') + in_db_service = False + new_lines = [] + for i, line in enumerate(lines): + if line.strip().startswith('db:'): + in_db_service = True + elif line.strip() and not line.startswith(' ') and not line.startswith('\t'): + in_db_service = False + if in_db_service and line.strip() == 'user: "1000:1000"': + continue + new_lines.append(line) + content = '\n'.join(new_lines) + + # Remove volumes section from db service + content = re.sub(r' volumes:\n - \.\/mysql\/data:\/var\/lib\/mysql\n', '', content) + + # Add tmpfs and healthcheck after stop_grace_period in db service + db_stop_grace = ' stop_grace_period: 1m' + db_additions = ' stop_grace_period: 1m\n tmpfs:\n - /var/lib/mysql\n healthcheck:\n test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "mempool", "-pmempool"]\n interval: 5s\n timeout: 5s\n retries: 10' + content = content.replace(db_stop_grace, db_additions, 1) + + # Add depends_on to web service after ports + web_ports = ' ports:\n - 8080:8080' + web_with_depends = ' ports:\n - 8080:8080\n depends_on:\n - api\n - db' + content = content.replace(web_ports, web_with_depends, 1) + + # Add depends_on to api service after command + api_command = ' command: "./wait-for-it.sh db:3306 --timeout=720 --strict -- ./start.sh"' + api_with_depends = ' command: "./wait-for-it.sh db:3306 --timeout=720 --strict -- ./start.sh"\n depends_on:\n - db' + content = content.replace(api_command, api_with_depends, 1) + + # Write the modified content + with open('docker-compose.test.yml', 'w') as f: + f.write(content) + + print("Generated docker-compose.test.yml") + SCRIPT_END + python3 /tmp/modify_compose.py cat docker-compose.test.yml - name: Start containers From a073003a1166e9a81b5b97e41288ea8dadb89da0 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Sat, 29 Nov 2025 19:27:16 -0800 Subject: [PATCH 7/9] Use proper health checks --- .github/workflows/docker.yml | 53 ++++++++++++++++++++++++++++-------- docker/docker-compose.yml | 17 ++++++++++++ 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index c1aeb6d3c..54b6dd552 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -280,9 +280,9 @@ jobs: # Remove volumes section from db service content = re.sub(r' volumes:\n - \.\/mysql\/data:\/var\/lib\/mysql\n', '', content) - # Add tmpfs and healthcheck after stop_grace_period in db service + # Add tmpfs after stop_grace_period in db service (healthcheck already exists in base file) db_stop_grace = ' stop_grace_period: 1m' - db_additions = ' stop_grace_period: 1m\n tmpfs:\n - /var/lib/mysql\n healthcheck:\n test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "mempool", "-pmempool"]\n interval: 5s\n timeout: 5s\n retries: 10' + db_additions = ' stop_grace_period: 1m\n tmpfs:\n - /var/lib/mysql' content = content.replace(db_stop_grace, db_additions, 1) # Add depends_on to web service after ports @@ -310,33 +310,62 @@ jobs: - name: Wait for services to be ready run: | - echo "Waiting for database to be ready..." + echo "Waiting for all services (web, api, db) to be healthy..." timeout=120 elapsed=0 while [ $elapsed -lt $timeout ]; do - if docker compose -f docker-compose.test.yml exec -T db mysqladmin ping -h localhost -u mempool -pmempool --silent 2>/dev/null; then - echo "Database is ready!" + # Check health status for all services + PS_OUTPUT=$(docker compose -f docker-compose.test.yml ps) + HEALTHY_COUNT=$(echo "$PS_OUTPUT" | grep -c "(healthy)" || true) + if [ "$HEALTHY_COUNT" -ge 3 ]; then + echo "All services are healthy!" + echo "$PS_OUTPUT" break fi + echo "Waiting for services to be healthy... (${elapsed}s/${timeout}s)" + echo "$PS_OUTPUT" sleep 2 elapsed=$((elapsed + 2)) done if [ $elapsed -ge $timeout ]; then - echo "Database did not become ready in time" + echo "Services did not become healthy in time" + docker compose -f docker-compose.test.yml ps docker compose -f docker-compose.test.yml logs exit 1 fi - - name: Verify containers are running + - name: Verify containers are healthy run: | - echo "Checking container status..." - docker compose -f docker-compose.test.yml ps - if ! docker compose -f docker-compose.test.yml ps | grep -q "Up"; then - echo "Some containers are not running" + echo "Checking container health status..." + PS_OUTPUT=$(docker compose -f docker-compose.test.yml ps) + echo "$PS_OUTPUT" + + # Check that all three services (web, api, db) are healthy + HEALTHY_COUNT=$(echo "$PS_OUTPUT" | grep -c "(healthy)" || true) + if [ "$HEALTHY_COUNT" -lt 3 ]; then + echo "Not all containers are healthy. Expected 3 healthy services, found $HEALTHY_COUNT" docker compose -f docker-compose.test.yml logs exit 1 fi - echo "All containers are running successfully!" + + # Verify each service individually for better error messages + if ! echo "$PS_OUTPUT" | grep -q "web.*(healthy)"; then + echo "Web service is not healthy" + docker compose -f docker-compose.test.yml logs web + exit 1 + fi + if ! echo "$PS_OUTPUT" | grep -q "api.*(healthy)"; then + echo "API service is not healthy" + docker compose -f docker-compose.test.yml logs api + exit 1 + fi + if ! echo "$PS_OUTPUT" | grep -q "db.*(healthy)"; then + echo "Database service is not healthy" + docker compose -f docker-compose.test.yml logs db + exit 1 + fi + + echo "All containers are healthy!" - name: Show container logs if: failure() diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml index 4e1094306..663af98e1 100644 --- a/docker/docker-compose.yml +++ b/docker/docker-compose.yml @@ -12,6 +12,12 @@ services: command: "./wait-for db:3306 --timeout=720 -- nginx -g 'daemon off;'" ports: - 80:8080 + healthcheck: + test: ["CMD-SHELL", "curl -f http://localhost:8080/ | grep -q ' Date: Sun, 30 Nov 2025 10:20:36 -0800 Subject: [PATCH 8/9] Refactor Docker workflows for PRs and tag pushes --- .github/workflows/docker.yml | 361 ++++++++++++++++++----------------- 1 file changed, 188 insertions(+), 173 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 54b6dd552..4eaa918b0 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -17,60 +17,14 @@ permissions: contents: read jobs: - build: - # Run on tag pushes OR on PRs that have the "docker" label (but not "docker-test") - if: | - github.event_name == 'push' || - (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker') && !contains(github.event.pull_request.labels.*.name, 'docker-test')) - strategy: - matrix: - service: - - frontend - - backend + test-images: + # Always run on tag pushes and all pull requests runs-on: ubuntu-latest - timeout-minutes: 120 - name: Build and push to DockerHub - outputs: - image-digest-frontend: ${{ matrix.service == 'frontend' && steps.docker-build.outputs.digest || '' }} - image-digest-backend: ${{ matrix.service == 'backend' && steps.docker-build.outputs.digest || '' }} - tag: ${{ matrix.service == 'frontend' && (steps.set-tag-push.outputs.tag || steps.set-tag-pr.outputs.tag) || '' }} + timeout-minutes: 30 + name: Test built Docker images steps: - - name: Replace the current swap file - shell: bash - run: | - sudo swapoff /mnt/swapfile || true - sudo rm -f /mnt/swapfile - sudo fallocate -l 16G /mnt/swapfile - sudo chmod 600 /mnt/swapfile - sudo mkswap /mnt/swapfile - sudo swapon /mnt/swapfile - - - name: Show current memory and swap status - shell: bash - run: | - sudo free -h - echo - sudo swapon --show - - - name: Mount a tmpfs over /var/lib/docker - shell: bash - run: | - if [ ! -d "/var/lib/docker" ]; then - echo "Directory '/var/lib/docker' not found" - exit 1 - fi - sudo mount -t tmpfs -o size=12G tmpfs /var/lib/docker - sudo systemctl restart docker - sudo df -h | grep docker - - # Only for tag pushes: use the Git tag as TAG - - name: Set TAG from pushed tag - if: github.event_name == 'push' - id: set-tag-push - run: | - TAG="${GITHUB_REF/refs\/tags\//}" - echo "TAG=${TAG}" >> $GITHUB_ENV - echo "tag=${TAG}" >> $GITHUB_OUTPUT + - name: Checkout project + uses: actions/checkout@v4 - name: Add SHORT_SHA env property with commit short sha run: | @@ -81,131 +35,20 @@ jobs: fi echo "SHORT_SHA=${SHA:0:8}" >> $GITHUB_ENV - - - name: Login to Docker for building - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - - - name: Checkout project - uses: actions/checkout@v4 - - # For PRs: use package.json version + short sha as TAG - - name: Set TAG from service package.json for pull requests - if: github.event_name == 'pull_request' - id: set-tag-pr + - name: Set TAG from pushed tag or package.json run: | - if [ "${{ matrix.service }}" = "frontend" ]; then - VERSION=$(jq -r '.version' frontend/package.json) + if [ "${{ github.event_name }}" = "push" ]; then + TAG="${GITHUB_REF/refs\/tags\//}" else - VERSION=$(jq -r '.version' backend/package.json) + FRONTEND_VERSION=$(jq -r '.version' frontend/package.json) + BACKEND_VERSION=$(jq -r '.version' backend/package.json) + if [ "$FRONTEND_VERSION" != "$BACKEND_VERSION" ]; then + echo "Error: Frontend version ($FRONTEND_VERSION) and backend version ($BACKEND_VERSION) do not match" + exit 1 + fi + TAG="v${FRONTEND_VERSION}-${SHORT_SHA}" fi - TAG="v${VERSION}-${SHORT_SHA}" echo "TAG=${TAG}" >> $GITHUB_ENV - echo "tag=${TAG}" >> $GITHUB_OUTPUT - - - name: Show set environment variables - run: | - printf " TAG: %s\n" "$TAG" - printf " SHORT_SHA: %s\n" "$SHORT_SHA" - - - name: Init repo for Dockerization - run: docker/init.sh "$TAG" - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - with: - platforms: linux/amd64,linux/arm64 - id: qemu - - - name: Setup Docker buildx action - uses: docker/setup-buildx-action@v3 - with: - platforms: linux/amd64,linux/arm64 - driver-opts: | - network=host - id: buildx - - - name: Available platforms - run: echo ${{ steps.buildx.outputs.platforms }} - - - name: Cache Docker layers - uses: actions/cache@v3 - id: cache - with: - path: /tmp/.buildx-cache - key: ${{ runner.os }}-buildx-${{ matrix.service }}-${{ github.sha }} - restore-keys: | - ${{ runner.os }}-buildx-${{ matrix.service }}- - - - name: Run Docker buildx for ${{ matrix.service }} against tag - id: docker-build - run: | - docker buildx build \ - --cache-from "type=local,src=/tmp/.buildx-cache" \ - --cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \ - --platform linux/amd64,linux/arm64 \ - --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ - --build-context rustgbt=./rust \ - --build-context backend=./backend \ - --output "type=registry,push=true" \ - --build-arg commitHash=$SHORT_SHA \ - ./${{ matrix.service }}/ - - tag-latest: - needs: build - # Only for successful *tag pushes* and only for "plain" versions (no '-') - if: ${{ needs.build.result == 'success' && github.event_name == 'push' && !contains(github.ref_name, '-') }} - runs-on: ubuntu-latest - timeout-minutes: 30 - name: Tag release build as latest - strategy: - matrix: - service: - - frontend - - backend - steps: - - name: Set env variables - run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV - - - name: Set up QEMU - uses: docker/setup-qemu-action@v3 - with: - platforms: linux/amd64,linux/arm64 - - - name: Setup Docker buildx action - uses: docker/setup-buildx-action@v3 - with: - platforms: linux/amd64,linux/arm64 - driver-opts: | - network=host - - - name: Login to Docker Hub - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin - - - name: Tag as latest for ${{ matrix.service }} - run: | - docker buildx imagetools create \ - --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \ - ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG - - test-images: - # Only run for PRs with "docker-test" label (independent of build job) - if: ${{ github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker-test') }} - runs-on: ubuntu-latest - timeout-minutes: 30 - name: Test built Docker images - steps: - - name: Checkout project - uses: actions/checkout@v4 - - - name: Add SHORT_SHA env property with commit short sha - run: | - SHA="${{ github.event.pull_request.head.sha }}" - echo "SHORT_SHA=${SHA:0:8}" >> $GITHUB_ENV - - - name: Set TAG from frontend package.json - run: | - VERSION=$(jq -r '.version' frontend/package.json) - echo "TAG=v${VERSION}-${SHORT_SHA}" >> $GITHUB_ENV - name: Show set environment variables run: | @@ -376,3 +219,175 @@ jobs: if: always() run: | docker compose -f docker-compose.test.yml down -v + + build: + needs: test-images + # Run on tag pushes OR on PRs with "docker-push" label (after test-images passes) + if: | + needs.test-images.result == 'success' && + (github.event_name == 'push' || + (github.event_name == 'pull_request' && contains(github.event.pull_request.labels.*.name, 'docker-push'))) + strategy: + matrix: + service: + - frontend + - backend + runs-on: ubuntu-latest + timeout-minutes: 120 + name: Build and push to DockerHub + outputs: + image-digest-frontend: ${{ matrix.service == 'frontend' && steps.docker-build.outputs.digest || '' }} + image-digest-backend: ${{ matrix.service == 'backend' && steps.docker-build.outputs.digest || '' }} + tag: ${{ matrix.service == 'frontend' && (steps.set-tag-push.outputs.tag || steps.set-tag-pr.outputs.tag) || '' }} + steps: + - name: Replace the current swap file + shell: bash + run: | + sudo swapoff /mnt/swapfile || true + sudo rm -f /mnt/swapfile + sudo fallocate -l 16G /mnt/swapfile + sudo chmod 600 /mnt/swapfile + sudo mkswap /mnt/swapfile + sudo swapon /mnt/swapfile + + - name: Show current memory and swap status + shell: bash + run: | + sudo free -h + echo + sudo swapon --show + + - name: Mount a tmpfs over /var/lib/docker + shell: bash + run: | + if [ ! -d "/var/lib/docker" ]; then + echo "Directory '/var/lib/docker' not found" + exit 1 + fi + sudo mount -t tmpfs -o size=12G tmpfs /var/lib/docker + sudo systemctl restart docker + sudo df -h | grep docker + + # Only for tag pushes: use the Git tag as TAG + - name: Set TAG from pushed tag + if: github.event_name == 'push' + id: set-tag-push + run: | + TAG="${GITHUB_REF/refs\/tags\//}" + echo "TAG=${TAG}" >> $GITHUB_ENV + echo "tag=${TAG}" >> $GITHUB_OUTPUT + + - name: Add SHORT_SHA env property with commit short sha + run: | + if [ "${{ github.event_name }}" = "pull_request" ]; then + SHA="${{ github.event.pull_request.head.sha }}" + else + SHA="${GITHUB_SHA}" + fi + echo "SHORT_SHA=${SHA:0:8}" >> $GITHUB_ENV + + + - name: Login to Docker for building + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + + - name: Checkout project + uses: actions/checkout@v4 + + # For PRs: use package.json version + short sha as TAG + - name: Set TAG from service package.json for pull requests + if: github.event_name == 'pull_request' + id: set-tag-pr + run: | + if [ "${{ matrix.service }}" = "frontend" ]; then + VERSION=$(jq -r '.version' frontend/package.json) + else + VERSION=$(jq -r '.version' backend/package.json) + fi + TAG="v${VERSION}-${SHORT_SHA}" + echo "TAG=${TAG}" >> $GITHUB_ENV + echo "tag=${TAG}" >> $GITHUB_OUTPUT + + - name: Show set environment variables + run: | + printf " TAG: %s\n" "$TAG" + printf " SHORT_SHA: %s\n" "$SHORT_SHA" + + - name: Init repo for Dockerization + run: docker/init.sh "$TAG" + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64 + id: qemu + + - name: Setup Docker buildx action + uses: docker/setup-buildx-action@v3 + with: + platforms: linux/amd64,linux/arm64 + driver-opts: | + network=host + id: buildx + + - name: Available platforms + run: echo ${{ steps.buildx.outputs.platforms }} + + - name: Cache Docker layers + uses: actions/cache@v3 + id: cache + with: + path: /tmp/.buildx-cache + key: ${{ runner.os }}-buildx-${{ matrix.service }}-${{ github.sha }} + restore-keys: | + ${{ runner.os }}-buildx-${{ matrix.service }}- + + - name: Run Docker buildx for ${{ matrix.service }} against tag + id: docker-build + run: | + docker buildx build \ + --cache-from "type=local,src=/tmp/.buildx-cache" \ + --cache-to "type=local,dest=/tmp/.buildx-cache,mode=max" \ + --platform linux/amd64,linux/arm64 \ + --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG \ + --build-context rustgbt=./rust \ + --build-context backend=./backend \ + --output "type=registry,push=true" \ + --build-arg commitHash=$SHORT_SHA \ + ./${{ matrix.service }}/ + + tag-latest: + needs: build + # Only for successful tag pushes (not PRs with docker-push label) and only for "plain" versions (no '-') + if: ${{ needs.build.result == 'success' && github.event_name == 'push' && !contains(github.ref_name, '-') }} + runs-on: ubuntu-latest + timeout-minutes: 30 + name: Tag release build as latest + strategy: + matrix: + service: + - frontend + - backend + steps: + - name: Set env variables + run: echo "TAG=${GITHUB_REF/refs\/tags\//}" >> $GITHUB_ENV + + - name: Set up QEMU + uses: docker/setup-qemu-action@v3 + with: + platforms: linux/amd64,linux/arm64 + + - name: Setup Docker buildx action + uses: docker/setup-buildx-action@v3 + with: + platforms: linux/amd64,linux/arm64 + driver-opts: | + network=host + + - name: Login to Docker Hub + run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + + - name: Tag as latest for ${{ matrix.service }} + run: | + docker buildx imagetools create \ + --tag ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:latest \ + ${{ secrets.DOCKER_HUB_USER }}/${{ matrix.service }}:$TAG From f019e9939443b52d71f8ef33b64b5cab335ee928 Mon Sep 17 00:00:00 2001 From: Felipe Knorr Kuhn Date: Tue, 13 Jan 2026 15:04:28 -0800 Subject: [PATCH 9/9] Switch to the Docker login action --- .github/workflows/docker.yml | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml index 4eaa918b0..212dfa9c3 100644 --- a/.github/workflows/docker.yml +++ b/.github/workflows/docker.yml @@ -288,7 +288,10 @@ jobs: - name: Login to Docker for building - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Checkout project uses: actions/checkout@v4 @@ -384,7 +387,10 @@ jobs: network=host - name: Login to Docker Hub - run: echo "${{ secrets.DOCKER_PASSWORD }}" | docker login -u "${{ secrets.DOCKER_USERNAME }}" --password-stdin + uses: docker/login-action@v3 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_PASSWORD }} - name: Tag as latest for ${{ matrix.service }} run: |