Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 5 additions & 4 deletions .github/dependabot.yml
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
version: 2
updates:
# Only the `FROM` tag in Dockerfile is visible here. ENV NODE_VERSION and the
# corepack pin are not, and stay manual. Daily because a new Amazon Linux 2023
# snapshot is a single ungrouped dependency, so a run without one is a no-op.
# Covers both `FROM` lines in Dockerfile. Daily because a new Amazon Linux
# 2023 snapshot is a single ungrouped dependency, so a run without one is a
# no-op. A Vite+ image bump should land with the matching vite-plus catalog
# bump, since the image's vp and the project's vite-plus are the same release.
#
# No `cooldown`: it needs a registry-assigned publication date, and Docker Hub's
# tag_last_pushed is currently the only accepted source. ECR Public has none, so
Expand Down Expand Up @@ -48,7 +49,7 @@ updates:
- dependency-name: "@babel/core"
update-types: ["version-update:semver-major"]
# @types/node tracks the Node major it describes, and we target Node LTS
# (24, pinned in Dockerfile, .node-version, and the engines fields). A major bump
# (24, pinned in .node-version and the engines fields). A major bump
# here would type against APIs the runtime does not have. Raise it by hand
# when we move to the next LTS.
- dependency-name: "@types/node"
Expand Down
7 changes: 7 additions & 0 deletions .github/workflows/test_build_docker.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ jobs:
run: |
docker run --rm --entrypoint="" test-image openssl --version

- name: Ensure Node.js matches .node-version
run: |
expected="v$(cat .node-version)"
actual=$(docker run --rm --entrypoint="" test-image node --version)
[ "$actual" = "$expected" ] || { echo "FAIL: image has $actual, .node-version is $expected"; exit 1; }
echo "✓ node $actual"

- name: Verify build tooling is absent
run: |
docker run --rm --entrypoint="" test-image sh -c '
Expand Down
62 changes: 27 additions & 35 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,58 +1,50 @@
# syntax=docker/dockerfile:1
# Pinned to a dated ECR Public snapshot for reproducible builds; bump to move forward.
# The tag also pins dnf repo metadata to that date, so the yum update below resolves
# against that snapshot and cannot reach anything published after it.
FROM public.ecr.aws/amazonlinux/amazonlinux:2023.12.20261013.0 AS os
ENV NODE_VERSION=24.21.0

# Node.js and pnpm for the build stages. The runtime copies only the node binary.
FROM os AS toolchain
RUN yum update -y && \
yum install -y tar xz && \
ARCH=$(uname -m) && \
if [ "$ARCH" = "x86_64" ]; then NODE_ARCH="x64"; \
elif [ "$ARCH" = "aarch64" ]; then NODE_ARCH="arm64"; \
else echo "Unsupported architecture: $ARCH" && exit 1; fi && \
curl -fsSL https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz | tar -xJ -C /usr/local --strip-components=1 && \
npm install --global corepack@0.36.0 && \
corepack enable
WORKDIR /graph-explorer
# The Vite+ image builds with the Node.js in .node-version and the pnpm in
# packageManager. Pinned by digest; keep the tag on the vite-plus catalog version.
FROM ghcr.io/voidzero-dev/vite-plus:1.1.0@sha256:15dedad6ab1f100c096aaebac6b0fb40ccf1f3dd0dd0169141e2a56588489b60 AS toolchain
WORKDIR /app

FROM toolchain AS build
# The image has no git, so skip installing the Vite+ commit hooks.
# The image has no repository to hook into, so skip the Vite+ commit hooks.
ENV VP_GIT_HOOKS=0
COPY . .
RUN pnpm install --frozen-lockfile && \
pnpm build && \
pnpm clean:dep && \
chmod a+x ./process-environment.sh ./setup-ssl.sh ./docker-entrypoint.sh
COPY --chown=vp:vp . .
RUN vp install --frozen-lockfile && \
vp run build && \
rm -rf node_modules packages/*/node_modules && \
chmod a+x ./process-environment.sh ./setup-ssl.sh ./docker-entrypoint.sh && \
NODE_HOME="$(dirname "$(dirname "$(vp env which node | head -1)")")" && \
mkdir /tmp/node && \
cp "$NODE_HOME/bin/node" "$NODE_HOME/LICENSE" /tmp/node/

# Installed from the manifests alone, so the layer is reused until a
# dependency changes and never holds devDependencies.
FROM toolchain AS deps
COPY package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc ./
COPY packages/graph-explorer/package.json packages/graph-explorer/
COPY packages/graph-explorer-proxy-server/package.json packages/graph-explorer-proxy-server/
COPY packages/shared/package.json packages/shared/
RUN pnpm install --prod --frozen-lockfile --ignore-scripts
COPY --chown=vp:vp package.json pnpm-lock.yaml pnpm-workspace.yaml .npmrc .node-version ./
COPY --chown=vp:vp packages/graph-explorer/package.json packages/graph-explorer/
COPY --chown=vp:vp packages/graph-explorer-proxy-server/package.json packages/graph-explorer-proxy-server/
COPY --chown=vp:vp packages/shared/package.json packages/shared/
RUN vp install --prod --frozen-lockfile --ignore-scripts

# Pinned to a dated ECR Public snapshot for reproducible builds; bump to move forward.
# The tag also pins dnf repo metadata to that date, so the yum update below resolves
# against that snapshot and cannot reach anything published after it.
# openssl is for setup-ssl.sh. yum, dnf, and python3 come with the base image
# and are stripped; npm, pnpm, and corepack never reach this stage.
FROM os AS runtime
# and are stripped.
FROM public.ecr.aws/amazonlinux/amazonlinux:2023.12.20261013.0 AS runtime
RUN yum update -y && \
yum install -y openssl && \
yum clean all && \
rpm -qa 'python3*' | while read pkg; do rpm -e --nodeps "$pkg"; done && \
rpm -qa 'dnf*' 'yum*' 'libdnf*' | while read pkg; do rpm -e --nodeps "$pkg"; done && \
rm -rf /var/cache/yum /var/cache/dnf
# LICENSE carries the notices for the third-party code built into node.
COPY --from=toolchain /usr/local/bin/node /usr/local/bin/node
COPY --from=toolchain /usr/local/LICENSE /usr/local/LICENSE
COPY --from=build --chown=0:0 /tmp/node/node /usr/local/bin/node
COPY --from=build --chown=0:0 /tmp/node/LICENSE /usr/local/LICENSE

ENV HOME=/graph-explorer
WORKDIR /graph-explorer
COPY --from=deps /graph-explorer ./
COPY --from=build /graph-explorer ./
COPY --from=deps --chown=0:0 /app ./
COPY --from=build --chown=0:0 /app ./

EXPOSE 443
EXPOSE 80
Expand Down
10 changes: 2 additions & 8 deletions docs/development.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ Build instructions and development setup for contributing to Graph Explorer. For

### Node Version

`.node-version` pins the Node.js version used for development and CI. Use [Vite+](https://viteplus.dev), [fnm](https://github.com/Schniz/fnm), or [nvm](https://github.com/nvm-sh/nvm) to switch to it:
`.node-version` pins the Node.js version used for development, CI, and the Docker image. Use [Vite+](https://viteplus.dev), [fnm](https://github.com/Schniz/fnm), or [nvm](https://github.com/nvm-sh/nvm) to switch to it:

```bash
vp env use # Vite+
Expand Down Expand Up @@ -46,14 +46,8 @@ Three files carry the pnpm version itself:
- `pnpm-lock.yaml`, which records the version under `packageManagerDependencies` plus an `integrity` for `pnpm` and for each `@pnpm/exe.*` platform package. `corepack use` regenerates this; never hand-edit it. Editing `packageManager` by hand still runs the version you typed, and rewrites these entries to match it, even under `--frozen-lockfile`. So the lockfile does not go stale, but the change lands in a commit nobody reviewed as a lockfile change.
- The pnpm requirement at the top of this document.

One more pins a _different tool_ whose version is tied to the pnpm major, so it stays put on a patch or minor bump and moves only when the major changes:

- `corepack@<version>` in the `Dockerfile`. The container gets pnpm only through `corepack enable`, and pnpm 12 ships as a native executable, so a new pnpm major needs a Corepack release that can fetch and verify that binary. A Corepack release's notes name the pnpm majors it handles.

Run `corepack use pnpm@<version>` from the repo root. It rewrites `packageManager` with a freshly computed integrity hash and then runs `pnpm install`, which updates the lockfile. Do not hand-write the hash. If `corepack` is not on your `PATH`, install it with `npm install -g corepack@<version>`; Node 24 bundles Corepack but only behind `corepack enable`, and later Node lines drop it entirely.

On a major bump, raise the `Dockerfile`'s Corepack pin _before_ running `corepack use`, because the Corepack currently pinned is by definition the one that predates the new pnpm major. Nothing in the commands below builds the image, so a Corepack pin too old to fetch the new pnpm surfaces only when `test_build_docker.yml` runs on the pull request. Run `docker build .` locally if you would rather find out sooner.

Do not reach for `pnpm self-update`. It rewrites `packageManager` without the `+sha512` hash, throwing the integrity pin away. `corepack use` is the only command that writes a correct pin.

Update this document to the same version, then confirm nothing shifted:
Expand All @@ -64,7 +58,7 @@ pnpm checks
pnpm test
```

`.github/workflows/ci.yml` gets pnpm through Vite+ with [`voidzero-dev/setup-vp`](https://github.com/voidzero-dev/setup-vp), which reads the version from `packageManager`, so a pnpm bump needs no workflow edit. On a major bump, check the "Set up Vite+" step on the pull request: if the pinned Vite+ cannot run the new pnpm major, upgrade Vite+ first.
`.github/workflows/ci.yml` and the `Dockerfile` both get pnpm through Vite+, which reads the version from `packageManager`, so a pnpm bump needs no workflow or `Dockerfile` edit. CI uses [`voidzero-dev/setup-vp`](https://github.com/voidzero-dev/setup-vp), and the image builds in the Vite+ image. On a major bump, check the "Set up Vite+" step and the Docker build on the pull request: if the pinned Vite+ cannot run the new pnpm major, upgrade Vite+ first.

No check in this repo compares the `+sha512` hash against anything, because every tool that installs pnpm already verifies the bytes it fetches and fails closed. Corepack and Vite+ both read the hash and refuse a `pnpm` package that does not match it. pnpm ignores the hash and instead checks each download against the `integrity` in `pnpm-lock.yaml` and against npm's registry signature, so a tampered lockfile stops the install with "its npm registry signature could not be verified" rather than passing quietly.

Expand Down
Loading