diff --git a/.github/workflows/containerize.yaml b/.github/workflows/containerize.yaml index 1bb5e48..f6b6113 100644 --- a/.github/workflows/containerize.yaml +++ b/.github/workflows/containerize.yaml @@ -73,3 +73,24 @@ jobs: file: 'Dockerfile' tags: ${{ steps.meta.outputs.tags }} labels: ${{ steps.meta.outputs.labels }} + + # Just make sure the container can start-up and responds to the health check + test-image: + needs: [build-image] + runs-on: ubuntu-latest + + services: + matrix-public-archive: + image: ${{ needs.build-image.outputs.docker_image_name }}:sha-${{ github.sha }} + credentials: + username: ${{ github.actor }} + password: ${{ secrets.GITHUB_TOKEN }} + ports: + - 3050:3050 + env: + matrixServerUrl: http://FAKE_SERVER/ + matrixAccessToken: FAKE_TOKEN + + steps: + - name: See if the container will respond to a request + run: curl http://localhost:3050/health-check diff --git a/Dockerfile b/Dockerfile index d40ec7c..ae3c9b5 100644 --- a/Dockerfile +++ b/Dockerfile @@ -6,12 +6,16 @@ WORKDIR /app RUN npm install npm@^8 --location=global +# Copy the health-check script +COPY docker-health-check.js /app/ + # Copy just what we need to install the dependencies so this layer can be cached # in the Docker build COPY package.json package-lock.json /app/ RUN npm install # Copy what we need for the client-side build +COPY config /app/config/ COPY public /app/public/ COPY shared /app/shared/ COPY vite.config.js /app/ @@ -21,4 +25,6 @@ RUN npm run build # Copy the rest of the app COPY server /app/server/ -ENTRYPOINT ["npm" "start"] +HEALTHCHECK CMD node docker-health-check.js + +ENTRYPOINT ["/bin/bash", "-c", "npm start"] diff --git a/docker-health-check.js b/docker-health-check.js new file mode 100644 index 0000000..fb17bf8 --- /dev/null +++ b/docker-health-check.js @@ -0,0 +1,22 @@ +'use strict'; + +const assert = require('assert'); + +const { fetchEndpointAsJson } = require('./server/lib/fetch-endpoint'); + +const config = require('./server/lib/config'); +const basePort = config.get('basePort'); +assert(basePort); + +const healthCheckUrl = `http://localhost:${basePort}/health-check`; + +(async () => { + try { + await fetchEndpointAsJson(healthCheckUrl); + process.exit(0); + } catch (err) { + // eslint-disable-next-line no-console + console.log(`Health check error: ${healthCheckUrl}`, err); + process.exit(1); + } +})(); diff --git a/server/lib/fetch-endpoint.js b/server/lib/fetch-endpoint.js index cc25dad..158e2c4 100644 --- a/server/lib/fetch-endpoint.js +++ b/server/lib/fetch-endpoint.js @@ -48,15 +48,15 @@ async function fetchEndpointAsText(endpoint, options) { async function fetchEndpointAsJson(endpoint, options) { const opts = { - ...options, + ...(options || {}), headers: { Accept: 'application/json', 'Content-Type': 'application/json', - ...(options.headers || {}), + ...(options?.headers || {}), }, }; - if (options.body) { + if (options?.body) { opts.body = JSON.stringify(options.body); } diff --git a/server/routes/install-routes.js b/server/routes/install-routes.js index cef798b..9755d89 100644 --- a/server/routes/install-routes.js +++ b/server/routes/install-routes.js @@ -63,6 +63,10 @@ function parseArchiveRangeFromReq(req) { } function installRoutes(app) { + app.get('/health-check', async function (req, res) { + res.send('{ "ok": true }'); + }); + // We have to disable no-missing-require lint because it doesn't take into // account `package.json`. `exports`, see // https://github.com/mysticatea/eslint-plugin-node/issues/255