The first deploy put all three Sites up serving and permanently unhealthy: every healthcheck run died with `/bin/sh: syntax error: unterminated quoted string`. The unit file carried the closing quote, but podman's stored config did not. Quadlet's parser strips a trailing double quote, so `node -e "fetch(...)"` reached the container as an unterminated shell string. The sibling apps never hit this because their HealthCmd is a bare `curl`. The check is now `wget -q -O /dev/null http://127.0.0.1:<port>/healthz` — busybox wget, no quoting, non-zero on anything but a 2xx — and the Dockerfile's own HEALTHCHECK says the same thing. The vars comment, the unit template and the deployment readme all say why it has to stay quote-free. Found from the running container rather than the play output: `podman healthcheck run` returned 1, `.State.Health.Log` held the sh error, and the app log showed it listening on 127.0.0.1:3000 the whole time.
44 lines
1.6 KiB
Docker
44 lines
1.6 KiB
Docker
# Built with Podman/Buildah, not Docker — see the Makefile. The file keeps the
|
|
# Dockerfile name because podman reads it as readily as a Containerfile, and the
|
|
# sibling apps use the same name.
|
|
#
|
|
# The app runs TypeScript directly on Node 24, so there is no build step and no
|
|
# compiler in the image. Node 24 on Alpine is musl, which sharp ships prebuilt
|
|
# binaries for.
|
|
FROM node:24-alpine
|
|
|
|
ENV NODE_ENV=production
|
|
WORKDIR /app
|
|
|
|
# Dependencies first, so editing source does not reinstall them.
|
|
COPY --chown=node:node package.json package-lock.json ./
|
|
RUN npm ci --omit=dev && npm cache clean --force
|
|
|
|
COPY --chown=node:node src ./src
|
|
COPY --chown=node:node public ./public
|
|
COPY --chown=node:node config ./config
|
|
COPY --chown=node:node scripts ./scripts
|
|
|
|
# Config is mounted read-only; everything the app writes lives under MENU_ROOT.
|
|
ENV MENU_ROOT=/data
|
|
ENV MENU_SITE_CONFIG=/etc/menu-app/site.json
|
|
ENV PORT=3000
|
|
|
|
RUN mkdir -p /data && chown node:node /data
|
|
USER node
|
|
|
|
EXPOSE 3000
|
|
|
|
# podman builds an OCI image by default, which drops this instruction, so the
|
|
# Quadlet unit declares the same check again as HealthCmd= (see
|
|
# ansible/roles/menu-app/templates/menu-app.container.j2). Keeping it here means
|
|
# the image still healthchecks itself when run directly.
|
|
#
|
|
# The check is deliberately quote-free: a Quadlet HealthCmd that ends in a double
|
|
# quote loses it, so the unit would run an unterminated shell string. wget is
|
|
# busybox's and exits non-zero on anything but a 2xx.
|
|
HEALTHCHECK --interval=30s --timeout=5s --start-period=10s \
|
|
CMD wget -q -O /dev/null http://127.0.0.1:${PORT:-3000}/healthz
|
|
|
|
CMD ["node", "src/server.ts"]
|