87 lines
3.4 KiB
Docker
87 lines
3.4 KiB
Docker
# syntax=docker/dockerfile:1
|
|||
|
|
###############################################################################
|
||
|
|
# SHARP-ER CMS - Strapi 5 on Node 22
|
||
|
|
#
|
||
|
|
# The build context is the REPOSITORY ROOT, not apps/cms: this is a pnpm
|
||
|
|
# workspace, so pnpm-lock.yaml, pnpm-workspace.yaml and .npmrc all live one
|
||
|
|
# level up and pnpm refuses to do a frozen install without them.
|
||
|
|
#
|
||
|
|
# docker build -f apps/cms/Dockerfile -t sharper-cms .
|
||
|
|
# docker compose build cms # does exactly that
|
||
|
|
#
|
||
|
|
# Debian slim rather than Alpine: Strapi's image pipeline (sharp) ships glibc
|
||
|
|
# prebuilt binaries, so this avoids a source build of libvips.
|
||
|
|
###############################################################################
|
||
|
|
|
||
|
|
# --- base: the toolchain both stages share ----------------------------------
|
||
|
|
FROM node:22-bookworm-slim AS base
|
||
|
|
ENV PNPM_VERSION=9.12.3
|
||
|
|
RUN apt-get update \
|
||
|
|
&& apt-get install -y --no-install-recommends ca-certificates \
|
||
|
|
&& rm -rf /var/lib/apt/lists/* \
|
||
|
|
&& npm install --global "pnpm@${PNPM_VERSION}"
|
||
|
|
WORKDIR /repo
|
||
|
|
|
||
|
|
|
||
|
|
# --- build: install dependencies and compile the admin panel ----------------
|
||
|
|
FROM base AS build
|
||
|
|
|
||
|
|
# Manifests first, so the (slow) install layer is cached until a dependency
|
||
|
|
# actually changes. Both workspace manifests are copied even though only the
|
||
|
|
# CMS is installed: pnpm compares the lockfile against every project in the
|
||
|
|
# workspace and calls the lockfile stale if one is missing.
|
||
|
|
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
|
||
|
|
COPY apps/cms/package.json ./apps/cms/
|
||
|
|
COPY apps/web/package.json ./apps/web/
|
||
|
|
|
||
|
|
# `--filter cms...` = the CMS and anything in the workspace it depends on.
|
||
|
|
# devDependencies are needed here: `strapi build` compiles TypeScript.
|
||
|
|
RUN pnpm install --frozen-lockfile --filter cms...
|
||
|
|
|
||
|
|
COPY apps/cms ./apps/cms
|
||
|
|
|
||
|
|
# `strapi build` loads config/*.ts, which reads these. They are throwaway
|
||
|
|
# build-time values that never reach the runtime stage below - the real ones
|
||
|
|
# are injected by docker compose at start-up.
|
||
|
|
ENV NODE_ENV=production \
|
||
|
|
APP_KEYS=build-only-key-1,build-only-key-2 \
|
||
|
|
API_TOKEN_SALT=build-only \
|
||
|
|
ADMIN_JWT_SECRET=build-only \
|
||
|
|
JWT_SECRET=build-only \
|
||
|
|
TRANSFER_TOKEN_SALT=build-only \
|
||
|
|
ENCRYPTION_KEY=build-only \
|
||
|
|
DATABASE_CLIENT=sqlite
|
||
|
|
RUN pnpm --filter cms build
|
||
|
|
|
||
|
|
|
||
|
|
# --- runtime ----------------------------------------------------------------
|
||
|
|
FROM base AS runtime
|
||
|
|
|
||
|
|
ENV NODE_ENV=production \
|
||
|
|
HOST=0.0.0.0 \
|
||
|
|
PORT=1337
|
||
|
|
|
||
|
|
# node_modules is carried over rather than reinstalled with --prod: the Strapi
|
||
|
|
# CLI still resolves its TypeScript toolchain at start-up, so pruning
|
||
|
|
# devDependencies breaks `strapi start` on a TypeScript project.
|
||
|
|
COPY --from=build /repo/node_modules ./node_modules
|
||
|
|
COPY --from=build /repo/package.json /repo/pnpm-workspace.yaml /repo/.npmrc ./
|
||
|
|
COPY --from=build /repo/apps/cms ./apps/cms
|
||
|
|
|
||
|
|
# The Media Library writes here. Ownership is set before the named volume is
|
||
|
|
# mounted, so the volume inherits it on first start.
|
||
|
|
RUN mkdir -p /repo/apps/cms/public/uploads \
|
||
|
|
&& chown -R node:node /repo/apps/cms
|
||
|
|
|
||
|
|
USER node
|
||
|
|
WORKDIR /repo/apps/cms
|
||
|
|
EXPOSE 1337
|
||
|
|
|
||
|
|
# Strapi answers /_health with 204 once the HTTP server is listening. Node 22
|
||
|
|
# has fetch built in, so the image needs no curl or wget.
|
||
|
|
HEALTHCHECK --interval=15s --timeout=5s --start-period=90s --retries=10 \
|
||
|
|
CMD node -e "fetch('http://127.0.0.1:'+(process.env.PORT||1337)+'/_health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))"
|
||
|
|
|
||
|
|
# `strapi start` serves the API and the pre-built admin panel from dist/.
|
||
|
|
CMD ["pnpm", "start"]
|