|
1 | | -name: Docker Compose Deploy |
| 1 | +name: Publish Docker images |
| 2 | + |
| 3 | +# Builds every v6y service image and pushes it to the GitHub Container Registry |
| 4 | +# so a deployment target can pull them. |
| 5 | +# |
| 6 | +# Tagging: |
| 7 | +# push to main -> main, sha-<short>, latest |
| 8 | +# release -> <release tag>, latest |
| 9 | +# latest tracks main because releases are cut rarely; pin to sha-<short> or to a |
| 10 | +# release tag when a deployment must not move. |
| 11 | +# |
| 12 | +# This workflow only builds and pushes. It deliberately performs no deployment: |
| 13 | +# the previous version ran "docker-compose up -d" on the ephemeral GitHub |
| 14 | +# runner, which started containers that were destroyed along with the runner. |
2 | 15 |
|
3 | 16 | on: |
4 | 17 | push: |
5 | 18 | branches: ["main"] |
6 | 19 | release: |
7 | | - types: ["created"] |
8 | | - branches: ["main"] |
| 20 | + types: ["published"] |
| 21 | + workflow_dispatch: |
9 | 22 |
|
10 | 23 | env: |
11 | 24 | REGISTRY: ghcr.io |
12 | 25 |
|
| 26 | +# A superseded main build is never worth finishing. |
| 27 | +concurrency: |
| 28 | + group: docker-publish-${{ github.ref }} |
| 29 | + cancel-in-progress: true |
| 30 | + |
13 | 31 | jobs: |
14 | | - deploy: |
| 32 | + publish: |
15 | 33 | runs-on: ubuntu-latest |
16 | 34 |
|
17 | 35 | permissions: |
18 | 36 | contents: read |
19 | 37 | packages: write |
20 | | - id-token: write |
21 | 38 |
|
22 | 39 | steps: |
23 | 40 | - name: Checkout repository |
24 | 41 | uses: actions/checkout@v4 |
25 | 42 |
|
26 | | - - name: Resolve environment secrets |
27 | | - uses: ./.github/actions/resolve-env-secrets |
28 | | - with: |
29 | | - prod-psql-db-host: ${{ secrets.PROD_PSQL_DB_HOST }} |
30 | | - prod-psql-db-name: ${{ secrets.PROD_PSQL_DB_NAME }} |
31 | | - prod-psql-db-user: ${{ secrets.PROD_PSQL_DB_USER }} |
32 | | - prod-psql-db-password: ${{ secrets.PROD_PSQL_DB_PASSWORD }} |
33 | | - prod-psql-db-port: ${{ secrets.PROD_PSQL_DB_PORT }} |
34 | | - prod-gitlab-private-token: ${{ secrets.PROD_GITLAB_PRIVATE_TOKEN }} |
35 | | - prod-github-private-token: ${{ secrets.PROD_GITHUB_PRIVATE_TOKEN }} |
36 | | - prod-jwt-secret: ${{ secrets.PROD_JWT_SECRET }} |
37 | | - prod-v6y-static-analyzer-api-path: ${{ secrets.PROD_V6Y_STATIC_ANALYZER_API_PATH }} |
38 | | - prod-v6y-static-analyzer-api-port: ${{ secrets.PROD_V6Y_STATIC_ANALYZER_API_PORT }} |
39 | | - prod-v6y-devops-api-path: ${{ secrets.PROD_V6Y_DEVOPS_API_PATH }} |
40 | | - prod-v6y-devops-api-port: ${{ secrets.PROD_V6Y_DEVOPS_API_PORT }} |
41 | | - prod-v6y-dynamic-analyzer-api-path: ${{ secrets.PROD_V6Y_DYNAMIC_ANALYZER_API_PATH }} |
42 | | - prod-v6y-dynamic-analyzer-api-port: ${{ secrets.PROD_V6Y_DYNAMIC_ANALYZER_API_PORT }} |
43 | | - prod-v6y-main-api-path: ${{ secrets.PROD_V6Y_MAIN_API_PATH }} |
44 | | - prod-v6y-main-api-port: ${{ secrets.PROD_V6Y_MAIN_API_PORT }} |
45 | | - prod-v6y-bff-api-path: ${{ secrets.PROD_V6Y_BFF_API_PATH }} |
46 | | - prod-v6y-bff-api-port: ${{ secrets.PROD_V6Y_BFF_API_PORT }} |
47 | | - prod-next-public-v6y-bff-path: ${{ secrets.PROD_NEXT_PUBLIC_V6Y_BFF_PATH }} |
48 | | - prod-nextauth-url: ${{ secrets.PROD_NEXTAUTH_URL }} |
49 | | - dev-psql-db-host: ${{ secrets.DEV_PSQL_DB_HOST }} |
50 | | - dev-psql-db-name: ${{ secrets.DEV_PSQL_DB_NAME }} |
51 | | - dev-psql-db-user: ${{ secrets.DEV_PSQL_DB_USER }} |
52 | | - dev-psql-db-password: ${{ secrets.DEV_PSQL_DB_PASSWORD }} |
53 | | - dev-psql-db-port: ${{ secrets.DEV_PSQL_DB_PORT }} |
54 | | - dev-gitlab-private-token: ${{ secrets.DEV_GITLAB_PRIVATE_TOKEN }} |
55 | | - dev-github-private-token: ${{ secrets.DEV_GITHUB_PRIVATE_TOKEN }} |
56 | | - dev-jwt-secret: ${{ secrets.DEV_JWT_SECRET }} |
57 | | - dev-v6y-static-analyzer-api-path: ${{ secrets.DEV_V6Y_STATIC_ANALYZER_API_PATH }} |
58 | | - dev-v6y-static-analyzer-api-port: ${{ secrets.DEV_V6Y_STATIC_ANALYZER_API_PORT }} |
59 | | - dev-v6y-devops-api-path: ${{ secrets.DEV_V6Y_DEVOPS_API_PATH }} |
60 | | - dev-v6y-devops-api-port: ${{ secrets.DEV_V6Y_DEVOPS_API_PORT }} |
61 | | - dev-v6y-dynamic-analyzer-api-path: ${{ secrets.DEV_V6Y_DYNAMIC_ANALYZER_API_PATH }} |
62 | | - dev-v6y-dynamic-analyzer-api-port: ${{ secrets.DEV_V6Y_DYNAMIC_ANALYZER_API_PORT }} |
63 | | - dev-v6y-main-api-path: ${{ secrets.DEV_V6Y_MAIN_API_PATH }} |
64 | | - dev-v6y-main-api-port: ${{ secrets.DEV_V6Y_MAIN_API_PORT }} |
65 | | - dev-v6y-bff-api-path: ${{ secrets.DEV_V6Y_BFF_API_PATH }} |
66 | | - dev-v6y-bff-api-port: ${{ secrets.DEV_V6Y_BFF_API_PORT }} |
67 | | - dev-next-public-v6y-bff-path: ${{ secrets.DEV_NEXT_PUBLIC_V6Y_BFF_PATH }} |
68 | | - dev-nextauth-url: ${{ secrets.DEV_NEXTAUTH_URL }} |
69 | | - |
70 | | - - name: Install Docker |
| 43 | + # NEXT_PUBLIC_* values are inlined into the browser bundle by next build, |
| 44 | + # so this is the one value the images need at build time. Everything else |
| 45 | + # (database credentials, JWT secret, service ports) is read at runtime and |
| 46 | + # must be injected by the deployment target, never baked into a layer. |
| 47 | + # |
| 48 | + # The production value is used even for main builds: these images exist to |
| 49 | + # be deployed, and a NEXT_PUBLIC_ value is public by construction since it |
| 50 | + # ships to browsers. Targeting another environment means rebuilding with a |
| 51 | + # different NEXT_PUBLIC_V6Y_BFF_PATH. |
| 52 | + - name: Resolve the public BFF path baked into the front bundles |
71 | 53 | run: | |
72 | | - curl -fsSL https://get.docker.com -o get-docker.sh |
73 | | - sudo sh get-docker.sh |
74 | | - docker --version |
| 54 | + echo "NEXT_PUBLIC_V6Y_BFF_PATH=${{ secrets.PROD_NEXT_PUBLIC_V6Y_BFF_PATH }}" >> "$GITHUB_ENV" |
75 | 55 |
|
76 | | - - name: Install Docker Compose |
| 56 | + - name: Compute image tags |
| 57 | + id: tags |
77 | 58 | run: | |
78 | | - DOCKER_COMPOSE_VERSION=$(curl -s "https://api.github.com/repos/docker/compose/releases/latest" | grep '"tag_name":' | sed -E 's/.*"([^"]+)".*/\1/') |
79 | | - sudo curl -L "https://github.com/docker/compose/releases/download/$DOCKER_COMPOSE_VERSION/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose |
80 | | - sudo chmod +x /usr/local/bin/docker-compose |
81 | | - docker-compose --version |
82 | | -
|
83 | | - - name: Determine Image Tag |
84 | | - id: set-tag |
85 | | - run: | |
86 | | - if [[ "${{ github.event_name }}" == "push" ]]; then |
87 | | - echo "IMAGE_TAG=${{ github.sha }}" >> $GITHUB_ENV |
88 | | - elif [[ "${{ github.event_name }}" == "release" ]]; then |
89 | | - echo "IMAGE_TAG=${{ github.event.release.tag_name }}" >> $GITHUB_ENV |
| 59 | + set -euo pipefail |
| 60 | + short_sha="$(git rev-parse --short HEAD)" |
| 61 | + if [[ "${{ github.event_name }}" == "release" ]]; then |
| 62 | + tags="${{ github.event.release.tag_name }} latest" |
| 63 | + else |
| 64 | + tags="main sha-${short_sha} latest" |
90 | 65 | fi |
91 | | - shell: bash |
| 66 | + echo "IMAGE_TAGS=${tags}" >> "$GITHUB_ENV" |
| 67 | + echo "list=${tags}" >> "$GITHUB_OUTPUT" |
| 68 | + echo "Tagging every image with: ${tags}" |
| 69 | +
|
| 70 | + - name: Set up Docker Buildx |
| 71 | + uses: docker/setup-buildx-action@v3 |
92 | 72 |
|
93 | | - - name: Log into Docker registry |
| 73 | + - name: Log in to the container registry |
94 | 74 | uses: docker/login-action@v3 |
95 | 75 | with: |
96 | 76 | registry: ${{ env.REGISTRY }} |
97 | | - username: ${{ github.repository_owner }} |
| 77 | + username: ${{ github.actor }} |
98 | 78 | password: ${{ secrets.GITHUB_TOKEN }} |
99 | 79 |
|
100 | | - - name: Validate Docker Compose Configuration |
101 | | - run: docker-compose -f docker-compose.yml config |
102 | | - |
103 | | - - name: Build and push images using Docker Compose |
104 | | - run: | |
105 | | - export REGISTRY="ghcr.io/${{ github.repository_owner }}/" |
106 | | - export IMAGE_TAG="${{ env.IMAGE_TAG }}" |
107 | | - docker-compose -f docker-compose.yml build |
108 | | - docker-compose -f docker-compose.yml push |
| 80 | + - name: Build and push every service image |
| 81 | + uses: docker/bake-action@v5 |
| 82 | + with: |
| 83 | + files: docker-bake.hcl |
| 84 | + push: true |
| 85 | + # A shared cache scope is intentional: all targets descend from the |
| 86 | + # same install and build stages, so they warm each other's layers. |
| 87 | + set: | |
| 88 | + *.cache-from=type=gha,scope=v6y-images |
| 89 | + *.cache-to=type=gha,mode=max,scope=v6y-images |
| 90 | + env: |
| 91 | + REGISTRY: ${{ env.REGISTRY }}/${{ github.repository_owner }}/ |
| 92 | + IMAGE_TAGS: ${{ env.IMAGE_TAGS }} |
| 93 | + NEXT_PUBLIC_V6Y_BFF_PATH: ${{ env.NEXT_PUBLIC_V6Y_BFF_PATH }} |
| 94 | + SOURCE_COMMIT: ${{ github.sha }} |
109 | 95 |
|
110 | | - - name: Deploy services using Docker Compose |
111 | | - if: github.event_name == 'push' || github.event_name == 'release' |
| 96 | + - name: Summarise what was published |
112 | 97 | run: | |
113 | | - docker-compose -f docker-compose.yml pull |
114 | | - docker-compose -f docker-compose.yml up -d |
| 98 | + { |
| 99 | + echo "### Images published to ${REGISTRY}/${{ github.repository_owner }}" |
| 100 | + echo |
| 101 | + echo "Tags applied to each image: \`${{ steps.tags.outputs.list }}\`" |
| 102 | + echo |
| 103 | + for image in v6y-migrate v6y-bff v6y-bfb-main-analyzer \ |
| 104 | + v6y-bfb-static-code-auditor v6y-bfb-url-dynamic-auditor \ |
| 105 | + v6y-bfb-devops-auditor v6y-frontend v6y-frontend-bo; do |
| 106 | + echo "- \`${REGISTRY}/${{ github.repository_owner }}/${image}\`" |
| 107 | + done |
| 108 | + } >> "$GITHUB_STEP_SUMMARY" |
0 commit comments