Conversation
This repository releases by moving the pins in .env and writing a dated CHANGELOG.md entry, and since the Keep a Changelog adoption its own changes have somewhere to go - but nothing made anyone write them down. A pull request that changes what a deployment is built from now has to touch CHANGELOG.md or carry the no-changelog label. RELEASING.md says how a bump is done, what a release is here, and where this repository sits in the ecosystem's release order. CI pins actions/checkout to a commit SHA, drops the checkout's credentials from the runner, and runs with a read-only token. Dependabot watches the pinned actions and the Ubuntu base images of both Dockerfiles, so a base image bump arrives as a reviewable change rather than as whatever the tag resolved to on rebuild day.
…steps at real documentation CHANGELOG.md already says CI changes belong under [Unreleased], but the changelog check exempted anything under .github/. Add it to the pattern. RELEASING.md told the bumper to preserve deviations "the README documents"; the README documents none. Point at the rationale comments in config/ and the changelog instead, and give the exact command for the no-changelog label the check depends on, which the repository does not yet have. The dependabot comment promised an Ubuntu LTS bump; the docker updater offers any newer tag, so say base-image bump.
Images
- Build in one apt layer with the lists removed, without apt-get upgrade,
with shallow clones and a pip cache mount. Drop gcc-multilib: nothing needs
a 32-bit toolchain and the package does not exist on arm64, so the images
now build on Apple Silicon. Replace locales-all with locales plus
locale-gen for en_US.UTF-8. Add .dockerignore files.
- mcritweb no longer bakes FLASK_DEBUG=1 into the image and is served by
gunicorn (installed explicitly, requirements.txt does not list it) instead
of the Flask development server.
- The mcrit image is built once and shared by server and worker.
Runtime
- Entry scripts run under set -eu and exec their process; the mcrit services
get init: true so SIGTERM reaches Python, and docker compose stop returns
in well under a second instead of the ten-second timeout.
- Healthchecks on mongodb, mcrit-server and mcritweb, depends_on conditions
instead of sleep 1, restart: unless-stopped in production, ${VAR:?} guards
on every interpolated tag, config and nginx mounts read-only.
- MongoDB 5.0 (end of life) moves to 8.0; the README documents the stepped
upgrade an existing storage/mongodb needs. mongod logs to stdout.
- nginx pinned through NGINX_TAG instead of nginx:latest; the TLS config
serves TLS 1.2 and 1.3 with the Mozilla intermediate ciphers, drops DHE and
ssl_dhparam, and no longer requests HSTS preload.
- nginx/ssl/*.pem are untracked (placeholders kept as .example) so a filled-in
private key cannot be committed by accident.
Scripts and CI
- build.sh uses docker compose v2; reset.sh and clone_repositories.sh get
shebangs, set -euo pipefail and no cd juggling; test_build.sh drops
--no-cache and builds the compose image names.
- CI builds both images with Buildx and a GitHub Actions layer cache, reads
the tags from .env, and has a timeout.
Verified locally: both images build, the stack comes up healthy, the web UI
answers through nginx and gunicorn, the mcrit API reports 1.9.0, and the
test entrypoint passes the 191 upstream unit tests.
Both images carried their build toolchain into the runtime and ran everything as root: build-essential, python3-dev, git and the pip cache stayed in the final layer, and a compromise of a web request had uid 0 inside the container. Split each Dockerfile into a builder stage that installs into /opt/venv and a runtime stage that copies the venv and the source across and adds only what running needs. The MCRIT image goes from 1.19 GB to 714 MB and the MCRITweb image from 1.28 GB to 787 MB, and both end on uid 10001. Running unprivileged makes ./storage/mcritweb, a host bind mount, something the operator has to hand to that uid once; both MCRITweb entry scripts now check the directory and exit with the chown command in the message instead of failing later inside Flask. entry_test.sh no longer pip-installs pytest at startup, which an unprivileged user cannot do: the image carries pytest and the script runs it directly, so it needs no make either. The base images are pinned by digest rather than by the 24.04 tag, so a rebuild cannot silently pick up a different Ubuntu, and both images carry OCI source, version and licence labels plus a .git-revision file recording the upstream commit they were built from - a label cannot hold a value resolved during the build. On the compose side, every service gets no-new-privileges, a json-file log cap of 5 files of 50 MB through one x-logging anchor so a chatty container cannot fill the host's disk, and MCRIT_AUTH_TOKEN passed through to the server and the worker. CI gains a lint job running hadolint over both Dockerfiles, docker compose config over both compose files and shellcheck over every entry script; .hadolint.yaml records why apt and pip version pinning are not enforced here. The README gains a production checklist covering TLS, the API token pairing with MCRITweb, the instance directory ownership and backups.
The README had grown into a mix of a project pitch, a partial setup guide and three version-specific maintenance notes, while the things a first deployment actually needs - the uid the containers run as, where the version pins live, what to change for TLS - were scattered or absent. It also still used docker-compose and duplicated release information that CHANGELOG.md owns. Restructure it in the order an operator meets the deployment: what is run and where the pins are, requirements, first deployment, configuration, production, upgrading, development mode, and a pointer to CHANGELOG.md for the version history. The MongoDB 5.0 stepping procedure stays because it is a one-directional host-level operation; the MCRIT 1.7.0 migration note and the SMDA fingerprint note are dropped in favour of the changelog entry and the upstream migration guide, which are where a release states what it requires. Every command is a fenced block using docker compose, and every path, variable and script name is checked against the tree.
The shipped nginx.conf never enabled gzip, so every response NGINX proxied from MCRITweb went over the wire uncompressed: the login page at 15115 bytes and the bundled jquery.js at 89501 bytes, both highly compressible text. Enable gzip in the http block, with gzip_vary so caches key on the encoding, gzip_proxied any because every response here is a proxied one, a 1 KB floor so small responses are not paid for, and an explicit gzip_types list covering HTML, CSS, JavaScript, JSON and SVG. HTML is compressed by default and needs no entry, but listing text/plain and the rest is what makes the static assets and the API's JSON eligible. Measured against the running stack: /login drops from 15115 to 5137 bytes and /static/jquery.js from 89501 to 36046 bytes, and both responses come back with Content-Encoding: gzip and Vary: Accept-Encoding. Closes danielplohmann#9
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
This is stacked on #13 and will be rebased once that merges; everything before 94436d0 belongs to that pull request, so 94436d0 is the commit to read. The nginx.conf this repository ships never enabled gzip, so every response NGINX proxied from MCRITweb went over the wire uncompressed. On a running stack the login page is 15115 bytes of HTML and the bundled jquery.js is 89501 bytes of JavaScript, both of them the kind of text that compresses well, and both were served in full to every client on every uncached request.
The change enables gzip in the http block, so it applies to both site configurations rather than being repeated in each. gzip_proxied any is what makes it take effect at all here: every response this NGINX serves comes from an upstream, and the default only compresses a narrow set of proxied responses. gzip_vary adds the Accept-Encoding vary header so a cache in front cannot serve a compressed body to a client that did not ask for one, and gzip_min_length 1024 keeps small responses out of it, where the header overhead and the CPU are not repaid. The gzip_types list names CSS, JavaScript, JSON and SVG; HTML is compressed by default and needs no entry, but nothing else is eligible without being listed.
Measured against the stack built from this branch, with the containers running and NGINX in front: /login goes from 15115 to 5137 bytes and /static/jquery.js from 89501 to 36046 bytes, both answering with Content-Encoding: gzip and Vary: Accept-Encoding, and identical requests without Accept-Encoding still return the uncompressed bodies at their original sizes. docker compose config -q is clean on both compose files.
The compression level is left at the default of 1, which is where the ratio per unit of CPU is best for on-the-fly compression of dynamic pages; a deployment serving mostly static assets could raise it, but that is a tuning decision rather than a default. There is no gzip_static, since nothing here pre-compresses assets, and no Brotli, which the stock nginx image does not carry. MCRIT's API is behind the same NGINX only when reached through the web frontend, so the JSON entry in the type list matters mostly for MCRITweb's own XHR responses.
Closes #9