Dockerfile (3151B)
1 # deploy/Dockerfile - the conductor 2 # 3 # Build from the repository root, since the context is the whole project: 4 # docker build -f deploy/Dockerfile -t finwo/conductor . 5 # 6 # Alpine rather than the official node image, because that one is only 7 # published for amd64, arm64 and ppc64le, and this is meant to run on 8 # riscv64 as well. Alpine 3.23 carries node 24 on every architecture we 9 # publish for, so the base is the same everywhere and nothing has to be 10 # fetched from a second source at build time. 11 # 12 # State lives in /data: the sqlite database, the git mirrors, the log spool 13 # and, unless object storage is configured, artifacts. Mount a volume there 14 # or none of it survives a restart. 15 16 ARG ALPINE_VERSION=3.23 17 18 # npm is only needed to resolve dependencies, so it stays in this stage and 19 # out of the image that ships. 20 FROM alpine:${ALPINE_VERSION} AS deps 21 22 RUN apk add --no-cache nodejs npm 23 24 WORKDIR /app 25 26 # Dependencies before source, so editing code does not reinstall them. 27 # mysql2 and pg are optional and pure javascript; both are installed so a 28 # single image covers every database backend. 29 COPY package.json ./ 30 RUN npm install --omit=dev --no-audit --no-fund \ 31 && npm cache clean --force 32 33 # The bundle is the same bytes on every architecture, so it is built once on 34 # whatever is doing the building. esbuild has no riscv64 release, and 35 # emulating one to produce an identical file would be slow for nothing. 36 FROM --platform=$BUILDPLATFORM alpine:${ALPINE_VERSION} AS web 37 38 RUN apk add --no-cache nodejs npm 39 40 WORKDIR /app 41 42 # The dev dependencies the deps stage deliberately skipped. 43 COPY package.json ./ 44 RUN npm install --no-audit --no-fund 45 46 COPY web/ ./web/ 47 RUN node web/build.cjs 48 49 FROM alpine:${ALPINE_VERSION} 50 51 # git is needed for the mirrors the conductor reads pipelines and source 52 # archives out of. Everything else it does is node, and node's sqlite is 53 # built in, so there is no database client to install. 54 RUN apk add --no-cache ca-certificates git nodejs 55 56 # Fixed uid/gid so a mounted volume keeps working across rebuilds and 57 # hosts, where an automatic id would drift. 58 RUN addgroup -S -g 1000 conductor \ 59 && adduser -S -u 1000 -G conductor -h /app conductor 60 61 WORKDIR /app 62 63 COPY --from=deps /app/node_modules ./node_modules 64 COPY package.json ./ 65 COPY src/ ./src/ 66 COPY migrations/ ./migrations/ 67 COPY --from=web /app/web/dist ./web/dist 68 69 # Paths point into the volume rather than at the defaults, which are 70 # relative to the working directory. 71 ENV NODE_ENV=production \ 72 CONDUCTOR_HOST=0.0.0.0 \ 73 CONDUCTOR_PORT=8080 \ 74 CONDUCTOR_DATABASE_PATH=/data/conductor.db \ 75 CONDUCTOR_STORAGE_PATH=/data/storage \ 76 CONDUCTOR_MIRROR_PATH=/data/mirrors \ 77 CONDUCTOR_LOG_PATH=/data/logs 78 79 RUN mkdir -p /data && chown -R conductor:conductor /data 80 81 USER conductor 82 VOLUME ["/data"] 83 EXPOSE 8080 84 85 # No curl in the image, so the check is made with node itself. 86 HEALTHCHECK --interval=30s --timeout=5s --start-period=20s --retries=3 \ 87 CMD node -e "fetch('http://127.0.0.1:'+(process.env.CONDUCTOR_PORT||8080)+'/health').then(r=>process.exit(r.ok?0:1)).catch(()=>process.exit(1))" 88 89 CMD ["node", "src/conductor/index.js"]