Skip to content

chore(docker): slim image and drop runtime git - #1279

Merged
Mygod merged 3 commits into
WatWowMap:mainfrom
Fabio1988:chore/docker-slim-v2
Nov 4, 2026
Merged

Mygod merged 3 commits into
WatWowMap:mainfrom
Fabio1988:chore/docker-slim-v2

Conversation

@Fabio1988

@Fabio1988 Fabio1988 commented Nov 3, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Slims the Docker image from 3 GB → 608 MB uncompressed (~1.4 GB → ~207 MB compressed) and removes the runtime dependency on git / .git. Behavior stays the same for Docker, local docker build and bare-metal installs; see the comparison below.

:main today this PR
Uncompressed 3 GB 608 MB
Compressed (gzip) ~1.4 GB ~207 MB
Yarn download cache in image 2.2 GB 0
devDependencies in image yes no
.git / git binary yes no

Where the size went

  • yarn install left the yarn download cache (/usr/local/share/.cache, 2.2 GB) in the layer.
  • devDependencies (eslint, typescript, semantic-release, monaco-editor, …) shipped in the runtime image.
  • There was no .dockerignore.

Changes

  • Dockerfile: three stages, build → prune → runtime.
    • BuildKit cache mount for the yarn cache.
    • git is only installed in the build stage, for the git-URL dependencies chalkercli and passport-discord.
    • The runtime stage is a fresh node:22.23.4-alpine with production node_modules. Paths stay under /home/node, so existing compose mounts keep working.
    • NPM_CONFIG_PREFIX / PATH are unchanged, and the default is CMD ["yarn", "start"].
  • .dockerignore: new. Keeps .git, node_modules, dist and local configs/secrets (config/local.json, config/areas.json, server/src/configs/**, .env) out of the build context. CI images never contained those, and configs are mounted as documented.
  • Git metadata: the GIT_SHA / GIT_REF build args become env vars. The workflow's .gitsha / .gitref steps and the tracked empty placeholder files are removed.
  • checkForUpdates.js:
    • Reads the sha and ref from the env; bare-metal installs still use local git branch / git rev-parse.
    • Gets the remote sha from the GitHub API (application/vnd.github.sha) instead of git ls-remote.
    • Skips non-branch refs. PR images used to parse refs/pull/123/merge as branch "123".
  • Dependencies: yarn start rebuilds the client on every boot, baking in the user's title, theme, start location, GA, Sentry and favicon. So the build toolchain is a real runtime dependency:
    • vite, @vitejs/plugin-react-swc, rollup-plugin-delete, @sentry/vite-plugin and @rm/vite-plugins move to dependencies.
    • vite-plugin-checker is lazy-loaded (dev only) and stays a devDependency.
    • openai and gpt-tokenizer (only used by the CI locales:generate job) move to @rm/locales devDependencies. The unused generate re-export is dropped, so the server no longer loads them at boot. @rm/locales still calls dotenv.config() on import, which vite.config.js relies on for NODE_CONFIG_ENV → dist-*.
    • The unused monaco-editor devDependency is removed. The playground keeps working: @monaco-editor/loader loads Monaco from cdn.jsdelivr.net/npm/monaco-editor@0.55.1, and the npm copy (^0.41) was never bundled.
  • Sentry release name: when a Sentry auth token is configured, the vite plugin now uses SENTRY_RELEASE → GIT_SHA. It used to fall back to git rev-parse HEAD, which only worked because the old image shipped git + .git.
  • docker.yml:
    • Adds setup-buildx-action and bumps to login v3, metadata v5, build-push v6.
    • Passes the build args and uses the GHA layer cache (type=gha,mode=max).
    • provenance: false, so a single image manifest is still published (no attestation index).
    • Adds explicit permissions (contents: read, packages: write).

Intentional behavior changes

  • Update check (Docker): branches containing / are no longer truncated, PR images skip the check, and there's no false "new version" notice when the remote sha can't be fetched. The remote lookup is skipped entirely when the local sha is unknown. The remote lookup has a 10s timeout; git ls-remote had none, and startup awaits it.
  • Update check (API): the remote sha comes from api.github.com (unauthenticated limit: 60 requests/hour per IP, and the check runs once at startup). On bare-metal, a branch that doesn't exist upstream now logs Unable to get remote SHA instead of a false "new version available".
  • Local docker build without build args: the update check is skipped with a single info line, with no git calls and no network request. Old local builds ran git, failed with dubious ownership in repository at '/home/node', called git ls-remote without a timeout and printed a false "new version available". Pass --build-arg GIT_SHA=$(git rev-parse HEAD) --build-arg GIT_REF=$(git symbolic-ref HEAD) to enable it.
  • Image contents: devDependencies are no longer in the image, so yarn dev, yarn lint, yarn locales:generate, yarn test etc. don't work inside the container. yarn start, yarn server, yarn build, yarn masterfile and migrate:* do.
  • Manifest media type: buildx (needed for the GHA cache) pushes an OCI image manifest instead of Docker v2. It is still a single manifest, and current Docker, Podman and Watchtower all pull it fine.
  • yarn install warning: it now prints an unmet-peer warning for monaco-editor.

Old vs new comparison

The same mounted local.json (custom headerTitle, MariaDB) was used in every setup. Probes: /, /playground, /index.html, /favicon.ico, /locales/{en,de}/translation.json, a 404, /api/v1/config, /auth/discord, POST /graphql, the config baked into the bundle, and sha256 of every dist file.

Setup Old New Result
Local node (yarn start) main this branch HTTP byte-identical, all 56 dist files identical
Local docker build (no args, config mounted) 3.03 GB 608 MB HTTP byte-identical, dist identical, startup build bakes the config in both
CI-built image (yarn start) ghcr.io/watwowmap/reactmap:main fork CI build of this change Same status codes and payloads apart from the version-specific index.html / bundle; CI-built and local new images identical

Testing

  • On this commit, rebased on v1.57.1: yarn install --frozen-lockfile, yarn lint, yarn prettier, yarn config:check, yarn masterfile, yarn test and yarn build all pass. These cover the steps in lint.yml, config.yml and release.yml.
  • The docker image built from this commit starts against MariaDB: masterfile, vite build and locales run at startup, the server listens, the endpoints above respond, there's no git and no .git* files in the image, and Node is v22.23.4 (includes the CVE-2026-111497 fix).
  • The same change, before the rebase onto v1.57.1, passed the fork's PR workflow (lint ✓, docker build + push ✓). Note that the docker job is skipped for PRs from forks, so it first runs here after merge.

🤖 Generated with Claude Code

- Multi-stage Dockerfile (build -> prune -> runtime) with a BuildKit
  yarn cache mount, so the 2.2 GB yarn cache never lands in a layer
- Add .dockerignore (keeps .git, node_modules and local configs/secrets
  out of the build context; configs are mounted as documented)
- Pass GIT_SHA/GIT_REF as build args instead of .gitsha/.gitref files,
  and remove the dead placeholder files
- checkForUpdates: read sha/ref from env, fetch the remote sha via the
  GitHub API (no git binary needed), skip non-branch refs (PR images)
- Move vite + build plugins to dependencies since `yarn start` builds
  the client on boot; lazy-load vite-plugin-checker (dev only)
- Name the Sentry release from SENTRY_RELEASE/GIT_SHA (the plugin used
  `git rev-parse HEAD`, which only worked because the image had .git)
- Move openai/gpt-tokenizer to @rm/locales devDependencies and drop the
  unused `generate` re-export; keep loading .env in @rm/locales
- Remove unused monaco-editor devDependency (the playground loads
  Monaco from jsDelivr via @monaco-editor/loader)
- docker.yml: buildx, current action versions, GHA layer cache,
  provenance disabled to keep publishing a single manifest

Image: 3 GB -> 608 MB uncompressed (~1.4 GB -> ~207 MB compressed)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Nov 3, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-11-03T22:16:18.715816Z 21b0149 PR opened
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 21b0149371

ℹ️ About Codex in GitHub

Codex has been enabled to automatically review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

When you sign up for Codex through ChatGPT, Codex can also answer questions or update the PR, like "@codex address that feedback".

Comment thread server/src/services/checkForUpdates.js Outdated
*/
async function getCurrentBranch() {
const gitRef = process.env.GIT_REF?.trim()
if (gitRef) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge Handle an empty Docker ref before falling back to git

When an image is built with plain docker build ., the Dockerfile always defines GIT_REF but leaves it as an empty string. This truthiness check therefore falls through to git branch, even though the new runtime contains neither git nor .git; after logging that failure, it assumes main, repeats the git failure for the SHA, and performs an awaited GitHub fetch that can add the full 10-second timeout before the server starts. Treat the presence of the Docker metadata environment variable, even when empty, as a Docker build and return an empty branch so the intended no-argument update check is actually skipped.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in 6db8242. GIT_REF being defined at all (the Dockerfile always sets it, empty without --build-arg) now marks a Docker build, and Docker never falls back to git. An empty ref or sha skips the check. The remote lookup is also skipped whenever the local sha is unknown. In a plain docker build . image the check now exits in ~2 ms with no git call and no fetch.

Fabio1988 and others added 2 commits November 3, 2026 23:36
The Dockerfile always defines GIT_REF/GIT_SHA (empty without
--build-arg), so treat their presence as Docker and never fall back to
git, which the image doesn't ship. Also skip the remote lookup whenever
the local sha is unknown instead of awaiting a fetch whose result can't
be compared.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- checkout v7, setup-buildx v4, login v4, metadata v6, build-push v7
  (none of their breaking changes affect this workflow)
- BUILDX_METADATA_PROVENANCE=disabled drops the ~600 line provenance
  trace from the printed build metadata
- DOCKER_BUILD_RECORD_UPLOAD=false stops uploading a .dockerbuild
  artifact on every run; the job summary is kept

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@Mygod
Mygod merged commit f52aab0 into WatWowMap:main Nov 4, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants