diff --git a/.gitea/workflows/build-and-publish.yml b/.gitea/workflows/build-and-publish.yml index e2b8be4..3f49b64 100644 --- a/.gitea/workflows/build-and-publish.yml +++ b/.gitea/workflows/build-and-publish.yml @@ -12,16 +12,25 @@ jobs: - name: Checkout code uses: actions/checkout@v3 - - name: Build Docker Image + # The Dockerfile has two runnable stages and no sensible default: an + # untargeted build produces the last one (frontend) on its own, which + # then has nothing to proxy /api to. Both get published separately. + - name: Build Docker Images run: | - docker build -t git.alexav.gg/${{ gitea.repository }}:latest . - docker build -t git.alexav.gg/${{ gitea.repository }}:${{ gitea.sha }} . + docker build --target backend \ + -t git.alexav.gg/${{ gitea.repository }}:backend-latest \ + -t git.alexav.gg/${{ gitea.repository }}:backend-${{ gitea.sha }} . + docker build --target frontend \ + -t git.alexav.gg/${{ gitea.repository }}:frontend-latest \ + -t git.alexav.gg/${{ gitea.repository }}:frontend-${{ gitea.sha }} . - name: Login to Gitea Registry run: | echo "${{ secrets.RUNNER_TOKEN }}" | docker login git.alexav.gg -u ${{ gitea.actor }} --password-stdin - - name: Push Docker Image + - name: Push Docker Images run: | - docker push git.alexav.gg/${{ gitea.repository }}:latest - docker push git.alexav.gg/${{ gitea.repository }}:${{ gitea.sha }} + docker push git.alexav.gg/${{ gitea.repository }}:backend-latest + docker push git.alexav.gg/${{ gitea.repository }}:backend-${{ gitea.sha }} + docker push git.alexav.gg/${{ gitea.repository }}:frontend-latest + docker push git.alexav.gg/${{ gitea.repository }}:frontend-${{ gitea.sha }} diff --git a/Dockerfile b/Dockerfile index 2279b05..8b432ad 100644 --- a/Dockerfile +++ b/Dockerfile @@ -99,7 +99,9 @@ USER node WORKDIR /app/apps/frontend EXPOSE 5173 +# /healthz, not /: rendering / reaches the backend, so probing it would report +# a backend outage as a dead frontend, and Traefik drops unhealthy containers. HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ - CMD ["node", "-e", "fetch('http://127.0.0.1:'+(process.env.PORT||5173)+'/').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] + CMD ["node", "-e", "fetch('http://127.0.0.1:'+(process.env.PORT||5173)+'/healthz').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"] CMD ["node", "server.js", "--prod"] diff --git a/apps/frontend/server.js b/apps/frontend/server.js index 3858104..c492096 100644 --- a/apps/frontend/server.js +++ b/apps/frontend/server.js @@ -12,6 +12,11 @@ const backendOrigin = process.env.BACKEND_ORIGIN ?? "http://localhost:3000"; const app = express(); +// Liveness only, so it must not touch the backend: the container healthcheck +// reads this, and an unhealthy container is dropped by Traefik's Docker +// provider - which would take the router, and with it the certificate, down. +app.get("/healthz", (req, res) => res.status(200).type("text/plain").end("ok")); + /** @type {import("vite").ViteDevServer | undefined} */ let vite;