129 lines
5.7 KiB
Docker
129 lines
5.7 KiB
Docker
# Vanilla upstream Cronicle, packaged as a container image.
|
|
#
|
|
# Why this exists: the "official" cronicle/cronicle image is published by the
|
|
# cronicle-edge project and has not been rebuilt since 2025-02-24 (:latest ==
|
|
# :0.9.74) while upstream Cronicle ships roughly monthly. See README.md.
|
|
#
|
|
# Deliberately reproduces the layout of cronicle/cronicle:0.9.74 (recovered via
|
|
# `docker history`) so this is a drop-in replacement. Deviations are called out
|
|
# in comments below.
|
|
|
|
ARG ALPINE_VERSION=3.22
|
|
FROM alpine:${ALPINE_VERSION}
|
|
|
|
# Cronicle release to build, WITHOUT the leading "v" (e.g. 0.9.126).
|
|
# CI resolves this from upstream's latest GitHub release.
|
|
ARG CRONICLE_VERSION
|
|
|
|
# Upstream's exact package list, intentionally unmodified. This image is the
|
|
# runtime for every Shell Script job, so the package set is effectively the job
|
|
# environment -- trimming it to save a few MB risks breaking job scripts.
|
|
# Notably:
|
|
# procps - Cronicle shells out to a real `ps` for job CPU/memory
|
|
# monitoring; busybox ps is not sufficient.
|
|
# tini - reaps zombies left by job child processes (PID 1 duties).
|
|
# jq - used directly by at least one job script.
|
|
# util-linux, coreutils, bash - job scripts expect GNU/util-linux behaviour
|
|
# rather than busybox applets.
|
|
RUN apk add --no-cache \
|
|
acl \
|
|
bash \
|
|
coreutils \
|
|
curl \
|
|
git \
|
|
jq \
|
|
nodejs \
|
|
npm \
|
|
openssl \
|
|
procps \
|
|
tar \
|
|
tini \
|
|
util-linux
|
|
|
|
# Matches upstream's image env.
|
|
#
|
|
# CRONICLE_foreground / CRONICLE_echo are pixl-config env overrides (the
|
|
# CRONICLE_ prefix maps to config keys); together they keep the process in the
|
|
# foreground and stream the log to stdout, which is what makes `docker logs`
|
|
# useful.
|
|
#
|
|
# /usr/local/bin is on PATH because deployments commonly bind-mount a docker
|
|
# client there for jobs that drive sibling containers.
|
|
#
|
|
# DEVIATION: upstream also sets TZ=America/New_York. Omitted on purpose -- this
|
|
# image defaults to UTC, and the deployment sets TZ explicitly.
|
|
ENV CRONICLE_foreground=1 \
|
|
CRONICLE_echo=1 \
|
|
EDITOR=vi \
|
|
PATH=/opt/cronicle/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
|
|
|
|
RUN addgroup cronicle --gid 1099 \
|
|
&& adduser -D -h /opt/cronicle -u 1000 -G cronicle cronicle
|
|
|
|
WORKDIR /opt/cronicle
|
|
|
|
RUN test -n "$CRONICLE_VERSION" || { \
|
|
echo "ERROR: --build-arg CRONICLE_VERSION=<x.y.z> is required" >&2; \
|
|
exit 1; \
|
|
}
|
|
|
|
# Built from the GitHub release tarball rather than npm: upstream marked the
|
|
# package "private": true as of v0.9.126, so it is not published to the
|
|
# registry. -f makes a bad/missing tag fail loudly instead of piping an HTML
|
|
# error page into tar.
|
|
#
|
|
# `busybox tar`, not GNU tar, and deliberately so. Alpine 3.22's GNU tar
|
|
# restores directory modes via the fchmodat2() syscall. Docker's default seccomp
|
|
# profile denies syscalls it does not know about by returning EPERM, and
|
|
# releases before ~25.0 predate fchmodat2 -- so on an older daemon GNU tar
|
|
# reports "Cannot change mode ...: Operation not permitted" for every directory
|
|
# and then exits 2, failing the build. The files do all extract, but the exit
|
|
# code is fatal and the directory modes are left unset. Verified: identical
|
|
# extraction under `--security-opt seccomp=unconfined` produces zero errors.
|
|
# Busybox tar uses plain fchmodat(), is unaffected, exits 0, and reproduces the
|
|
# archive's modes exactly. GNU tar is still installed above for runtime parity.
|
|
RUN curl -fsSL "https://github.com/jhuckaby/Cronicle/archive/refs/tags/v${CRONICLE_VERSION}.tar.gz" -o /tmp/cronicle.tar.gz \
|
|
&& busybox tar xzf /tmp/cronicle.tar.gz --strip-components 1 -C /opt/cronicle \
|
|
&& rm /tmp/cronicle.tar.gz
|
|
|
|
# `npm ci`, not upstream's `npm install`, so the build is reproducible from the
|
|
# lockfile committed in the release tarball.
|
|
#
|
|
# --ignore-scripts skips the root package's "postinstall": "pixl-boot install",
|
|
# which registers Cronicle as a systemd/init service -- meaningless in a
|
|
# container. No *dependency* declares an install script (verified against the
|
|
# lockfile), so nothing else is being skipped, and no build toolchain is needed.
|
|
#
|
|
# --omit=dev drops pixl-unit, the only devDependency, which is otherwise dead
|
|
# weight in the runtime image. bin/build.js needs only async, pixl-tools and
|
|
# uglify-js, all of which are regular dependencies.
|
|
RUN npm ci --ignore-scripts --omit=dev
|
|
|
|
# Bundles and minifies the front end into htdocs/js/_combo.js. Required; the UI
|
|
# 404s on its assets without it.
|
|
RUN node bin/build dist
|
|
|
|
# Regression gate -- see test/newline-regression.js for the full explanation.
|
|
# Lives inside /opt/cronicle so node resolves pixl-json-stream from the real
|
|
# installed tree rather than a copy.
|
|
COPY test/newline-regression.js /opt/cronicle/newline-regression.js
|
|
RUN node /opt/cronicle/newline-regression.js \
|
|
&& rm /opt/cronicle/newline-regression.js
|
|
|
|
# Bind mounts normally land on these; create them with tight modes so a missing
|
|
# mount does not silently expose job data or secrets.
|
|
RUN mkdir -p /opt/cronicle/data /opt/cronicle/conf \
|
|
&& chmod 0700 /opt/cronicle/data /opt/cronicle/conf
|
|
|
|
LABEL org.opencontainers.image.title="Cronicle" \
|
|
org.opencontainers.image.description="Vanilla upstream Cronicle, built from the GitHub release tarball" \
|
|
org.opencontainers.image.version="${CRONICLE_VERSION}" \
|
|
org.opencontainers.image.url="https://github.com/jhuckaby/Cronicle" \
|
|
org.opencontainers.image.source="https://git.lerch.org/lobo/cronicle-docker" \
|
|
org.opencontainers.image.licenses="MIT"
|
|
|
|
# DEVIATION: upstream also COPYs bin/manager and bin/worker into the image.
|
|
# Those are cronicle-edge helper entrypoints; vanilla Cronicle is started with
|
|
# `control.sh start`, so they are omitted. No CMD is set, matching upstream --
|
|
# the caller supplies it.
|
|
ENTRYPOINT ["/sbin/tini", "--"]
|