@@ -65,69 +65,93 @@ jobs:
6565 username : ${{ github.actor }}
6666 password : ${{ secrets.GITHUB_TOKEN }}
6767
68- # Single-arch first, loaded into the local daemon so the next step can
69- # actually run it. The multi-arch build below reuses this layer cache, so
70- # the extra build is close to free.
71- #
72- # A boot smoke test is the only thing that exercises the real CMD, the
73- # real migrations and the real build args. Two production incidents got
74- # past a green build here: config/runtime.exs is evaluated at container
75- # start, not at build time, and a build arg renamed in the Dockerfile but
76- # not here is silently ignored - which shipped a SQLite build under the
77- # -postgres tag.
78- - name : Build image for the smoke test (amd64, loaded locally)
68+ - name : Build and (conditionally) push multi-arch image
7969 uses : docker/build-push-action@v4
8070 with :
8171 context : .
8272 file : Dockerfile
8373 build-args : |
8474 GAMEND_CONTENT_APP_VERSION=${{ env.APP_VERSION }}
8575 GAMEND_DB_ADAPTER=${{ matrix.adapter }}
76+ platforms : linux/amd64,linux/arm64
77+ # amd64 reuses the smoke build's layers; arm64 keeps its own scope.
78+ # Without this every run rebuilt the Rust toolchain, every dependency
79+ # and every NIF from scratch.
80+ cache-from : type=gha,scope=${{ matrix.adapter }}
81+ cache-to : type=gha,mode=max,scope=${{ matrix.adapter }}
82+ push : ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
83+ tags : |
84+ ${{ env.IMAGE }}:latest${{ matrix.tags_suffix }}
85+ ${{ env.IMAGE }}:${{ github.sha }}${{ matrix.extra_tag_suffix }}
86+
87+ smoke :
88+ name : Boot the image (${{ matrix.adapter }})
89+ runs-on : ubuntu-latest
90+
91+ # Deliberately independent of `build`: this proves the image starts and
92+ # serves traffic, which unit tests cannot. config/runtime.exs is evaluated
93+ # at container start, not at build time, so a green build says nothing
94+ # about whether the app boots. It does not gate publishing.
95+ strategy :
96+ fail-fast : false
97+ matrix :
98+ adapter : [sqlite, postgres]
99+
100+ steps :
101+ - uses : actions/checkout@v4
102+
103+ - uses : docker/setup-buildx-action@v2
104+
105+ - name : Build the image (amd64, loaded locally)
106+ uses : docker/build-push-action@v4
107+ with :
108+ context : .
109+ file : Dockerfile
110+ build-args : |
111+ GAMEND_DB_ADAPTER=${{ matrix.adapter }}
86112 platforms : linux/amd64
87113 load : true
88114 tags : gamend-smoke:${{ matrix.adapter }}
89- cache-from : type=gha,scope=${{ matrix.adapter }}-amd64
90- cache-to : type=gha,mode=max,scope=${{ matrix.adapter }}-amd64
91-
92- - name : Start a Postgres for the smoke test
93- if : matrix.adapter == 'postgres'
94- run : |
95- docker run -d --name smoke-db \
96- -e POSTGRES_PASSWORD=postgres \
97- -e POSTGRES_USER=postgres \
98- -e POSTGRES_DB=game_server_prod \
99- -p 5432:5432 postgres:15
100- for _ in $(seq 1 30); do
101- docker exec smoke-db pg_isready -U postgres && break
102- sleep 2
103- done
115+ cache-from : type=gha,scope=${{ matrix.adapter }}
116+ cache-to : type=gha,mode=max,scope=${{ matrix.adapter }}
104117
105- - name : Boot the image and check it serves traffic
118+ - name : Boot it and check it serves traffic
106119 run : |
107120 set -euo pipefail
108121
109- # An env file keeps this out of word-splitting, and keeps the
110- # docker run line readable whichever adapter is being smoked.
111- : > /tmp/smoke.env
112- echo "GAMEND_AUTH_SECRET_KEY_BASE=$(head -c 48 /dev/urandom | base64 | tr -d '\n')" >> /tmp/smoke.env
113- echo "GAMEND_HTTP_HOST=localhost" >> /tmp/smoke.env
114- echo "GAMEND_OBSERVABILITY_LOG_LEVEL=info" >> /tmp/smoke.env
122+ docker network create smoke-net
123+
124+ # POSTGRES_* are inherited names — platforms provision them, so they
125+ # are the only database variables the server reads. Using a derived
126+ # GAMEND_DB_POSTGRES_* name here silently configures nothing.
127+ {
128+ echo "GAMEND_AUTH_SECRET_KEY_BASE=$(head -c 48 /dev/urandom | base64 | tr -d '\n')"
129+ echo "GAMEND_HTTP_HOST=localhost"
130+ echo "GAMEND_OBSERVABILITY_LOG_LEVEL=info"
131+ } > smoke.env
115132
116133 if [ "${{ matrix.adapter }}" = "postgres" ]; then
134+ docker run -d --name smoke-db --network smoke-net \
135+ -e POSTGRES_PASSWORD=postgres \
136+ -e POSTGRES_USER=postgres \
137+ -e POSTGRES_DB=game_server_prod \
138+ postgres:15
139+
140+ for _ in $(seq 1 30); do
141+ docker exec smoke-db pg_isready -U postgres >/dev/null 2>&1 && break
142+ sleep 2
143+ done
144+
117145 {
118- echo "GAMEND_DB_POSTGRES_HOST=host.docker.internal "
119- echo "GAMEND_DB_POSTGRES_USER =postgres"
120- echo "GAMEND_DB_POSTGRES_PASSWORD =postgres"
121- echo "GAMEND_DB_POSTGRES_DB =game_server_prod"
122- } >> /tmp/ smoke.env
146+ echo "POSTGRES_HOST=smoke-db "
147+ echo "POSTGRES_USER =postgres"
148+ echo "POSTGRES_PASSWORD =postgres"
149+ echo "POSTGRES_DB =game_server_prod"
150+ } >> smoke.env
123151 fi
124152
125- # A real production environment: enough to enter the branches in
126- # config/host_runtime.exs that only prod takes.
127- docker run -d --name gamend-smoke \
128- --add-host=host.docker.internal:host-gateway \
129- -p 4000:4000 \
130- --env-file /tmp/smoke.env \
153+ docker run -d --name gamend-smoke --network smoke-net \
154+ -p 4000:4000 --env-file smoke.env \
131155 gamend-smoke:${{ matrix.adapter }}
132156
133157 for _ in $(seq 1 60); do
@@ -148,7 +172,7 @@ jobs:
148172 exit 1
149173
150174 # The build arg has to reach the compiled code, not just the shell: an
151- # ignored GAMEND_DB_ADAPTER used to publish a SQLite build as -postgres.
175+ # ignored GAMEND_DB_ADAPTER once published a SQLite build as -postgres.
152176 - name : Verify the image compiled against the expected adapter
153177 run : |
154178 set -euo pipefail
@@ -160,32 +184,12 @@ jobs:
160184 sqlite) echo "$adapter" | grep -q "Ecto.Adapters.SQLite3" ;;
161185 esac
162186
163- - name : Smoke test teardown
187+ - name : Teardown
164188 if : always()
165189 run : |
166190 docker logs gamend-smoke 2>&1 | tail -50 || true
167191 docker rm -f gamend-smoke smoke-db 2>/dev/null || true
168-
169- - name : Build and (conditionally) push multi-arch image
170- uses : docker/build-push-action@v4
171- with :
172- context : .
173- file : Dockerfile
174- build-args : |
175- GAMEND_CONTENT_APP_VERSION=${{ env.APP_VERSION }}
176- GAMEND_DB_ADAPTER=${{ matrix.adapter }}
177- platforms : linux/amd64,linux/arm64
178- # amd64 reuses the smoke build's layers; arm64 keeps its own scope.
179- # Without this every run rebuilt the Rust toolchain, every dependency
180- # and every NIF from scratch.
181- cache-from : |
182- type=gha,scope=${{ matrix.adapter }}-amd64
183- type=gha,scope=${{ matrix.adapter }}-arm64
184- cache-to : type=gha,mode=max,scope=${{ matrix.adapter }}-arm64
185- push : ${{ github.ref == 'refs/heads/main' && github.event_name == 'push' }}
186- tags : |
187- ${{ env.IMAGE }}:latest${{ matrix.tags_suffix }}
188- ${{ env.IMAGE }}:${{ github.sha }}${{ matrix.extra_tag_suffix }}
192+ docker network rm smoke-net 2>/dev/null || true
189193
190194 lint :
191195 name : Elixir lint
0 commit comments