Repository navigation
feat(web): a Dockerfile to self-host the web app - #974
Conversation
Lands the roadmap item "A Dockerfile for self-hosting the web app" (Self-contained, any time). The Dockerfile builds the web app as web-deploy.yml does (the same pinned, checksum-verified Trunk release, `trunk build --release` and `cargo xtask web-pwa`), with relative addresses as the docs preview's build, and serves it with the slim nginx-unprivileged image: non-root, on port 8080, with a health check. The build stage runs on the build machine's own platform, so an image for another platform, or for several, compiles the WebAssembly once, natively. The base images are pinned by tag and digest; a docker entry in dependabot.yml keeps them current. packaging/docker/nginx.conf caches the content-hashed .js and .wasm for good and revalidates everything else, so a new image reaches the app's new-version notice. It serves text as UTF-8, gzips (also behind proxies), gives the web app manifest its MIME type, sends nosniff and relative redirects, and leaves out X-Frame-Options because the Python package's widgets frame the app. Docs: "Self-host the web app with Docker" in docs/installation.md (including signing in to ArcGIS from a self-hosted copy), linked from docs/python.md and packaging/README.md; the comparison pages; ROADMAP.md (Done) and GEOLIBRE_PARITY.md (still partial: no published image or runtime config).
|
No actionable comments were generated in the recent review.
|
| Layer / File(s) | Summary |
|---|---|
Build the web app image .dockerignore, Dockerfile, .github/dependabot.yml |
The Docker build installs the required tools, verifies and downloads Trunk for supported architectures, then builds the web app and PWA. Docker build exclusions and weekly Docker dependency updates are added. |
Serve the built web app Dockerfile, packaging/docker/nginx.conf |
The final image serves the built assets with unprivileged nginx on port 8080. The configuration sets cache policies, compression, headers, file handling, manifest MIME type, and /healthz. |
Document Docker self-hosting docs/installation.md, packaging/README.md, GEOLIBRE_PARITY.md, ROADMAP.md, docs/comparison/index.md, docs/comparison/qgis.md, docs/python.md |
The installation guide and related documentation describe Docker self-hosting and update the project’s hosting descriptions and parity status. |
Priority:
Estimated code review effort: 3 (Moderate) | ~20 minutes
Change: Feature
Sequence Diagram(s)
sequenceDiagram
participant DockerBuild
participant Trunk
participant CargoXtask
participant Nginx
participant Browser
DockerBuild->>Trunk: Build web app
DockerBuild->>CargoXtask: Build PWA from dist/web
DockerBuild->>Nginx: Copy nginx config and built assets
Browser->>Nginx: Request web app
Nginx-->>Browser: Serve requested asset
Possibly related PRs
- opengeos/RustGIS#747: PR
#747added the roadmap and parity inventory that included the Docker self-hosting item addressed here.
Suggested reviewers: giswqs
Merge Risk: 
Minimal · up to 58c88
-
The Docker self-hosting path and its documented deployment configuration are consistent, with no established merge-blocking risk.
Pre-merge checks: 
5
-
Full details: Description check
-
Explanation
The description includes the required Summary, Testing, and Related issue sections. It explains the implementation, documents extensive validation, identifies the related roadmap item, and notes known limitations and follow-ups.
Full details: Docstring Coverage
-
Explanation
No eligible functions found in the changed files. Skipping docstring coverage check. Docstring coverage is scoped to functions touched by this diff. Analyzed 0 functions across 0 files. (1 skipped: 1 unsupported.)
-
Finishing Touches
-
🧪 Generate unit tests (beta)
-
- Create a new PR
-
- Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.
A rabbit checks the Dockerfile’s plan,
Trunk builds the web app as it can.
Nginx serves each page with care,
A health check answers from its lair.
Hop, the docs now show the way!
Comment @coderabbitai help to get the list of available commands.
| # own platform, also when the image is for another (`--platform linux/amd64,linux/arm64`). | ||
| FROM --platform=$BUILDPLATFORM rust:1.99.1-slim@sha256:1a939748a7cbab172de91a014e3804a97af1394a8c844faa67fce9401c4e40fb AS build | ||
|
|
||
| # clang compiles the Zstandard decoder in rustgis-data to WebAssembly; curl downloads Trunk and |
There was a problem hiding this comment.
Nit (comment accuracy, confidence high): the Dockerfile's curl does not fetch the Natural Earth data. crates/render/build.rs does that itself by running curl at build time (and panics without network unless RUSTGIS_DATA_DIR is set). The package is needed because build.rs shells out to it. Suggested wording:
| # clang compiles the Zstandard decoder in rustgis-data to WebAssembly; curl downloads Trunk and | |
| # clang compiles the Zstandard decoder in rustgis-data to WebAssembly; curl downloads Trunk, and | |
| # crates/render/build.rs uses curl to fetch the Natural Earth sample data it embeds. |
| RUN case "$BUILDARCH" in \ | ||
| amd64) arch=x86_64; sum=f2b4680cd239693a646a2795e4633c625328d7b2a044fbe749fa3a2fe9e7036b ;; \ | ||
| arm64) arch=aarch64; sum=b1d8e60e454f7fc182d9a4d95d1506ffbae947d8ba90f8f6f02da93b60f980f9 ;; \ | ||
| *) echo "no Trunk release for $BUILDARCH" >&2; exit 1 ;; \ |
There was a problem hiding this comment.
Nit (error message, confidence medium): if BUILDARCH is empty (a builder that doesn't predefine it, such as the legacy builder or some Podman/Buildah setups), this prints no Trunk release for with nothing after it. Naming the variable makes the failure diagnosable:
| *) echo "no Trunk release for $BUILDARCH" >&2; exit 1 ;; \ | |
| *) echo "no Trunk release for '${BUILDARCH:-an unset platform}' (BUILDARCH is set by BuildKit)" >&2; exit 1 ;; \ |
There was a problem hiding this comment.
Fixed in 58c8853: the message shows BUILDARCH's value, empty or not, and says BuildKit sets it.
| RUN --mount=type=cache,target=/usr/local/cargo/registry \ | ||
| --mount=type=cache,target=/src/target \ | ||
| --mount=type=cache,target=/root/.cache/trunk \ | ||
| trunk build --release --locked --public-url ./ |
There was a problem hiding this comment.
Note (consistency, confidence medium): the header says the image builds "as web-deploy.yml does", but the deploy runs trunk build --release --public-url / without --locked. The --public-url ./ change is intentional. --locked is a reasonable addition, but the header should say it differs, or the deploy workflow should adopt it too. The PR also adds no CI job that builds this Dockerfile, so a broken Dockerfile won't be caught before a release (the PR lists this as a follow-up).
There was a problem hiding this comment.
Fixed in 58c8853: the header now lists --locked with the other two differences from web-deploy.yml. A CI job that builds the image is offered as a follow-up in the description.
Code reviewReviewed the Dockerfile, Bugs: none found. Security: none found. The Trunk tarball is verified against a pinned SHA-256 before it is run, and nginx sets Performance: none found. Cache mounts and the Quality (posted inline, confidence in brackets):
AGENTS.md: The licence label ( Change sizeCompared with: the whole PR against main (merge base 6f4533e to PR head cf17971).
|
📖 Docs preview
GitHub Pages takes a minute or two to publish a new commit. Newer commits from the fork aren't published until a maintainer adds the |
The header lists --locked among the differences from web-deploy.yml, the apt comment says build.rs runs curl itself, and an unsupported or empty BUILDARCH is named in the error.
Summary
Adds a
Dockerfilethat builds the web app from the checkout and serves it with nginx, so anyone can host their own copy of web.rustgis.app. It lands the roadmap item A Dockerfile for self-hosting the web app (Self-contained, any time). No Rust code, crates, commands or project-format changes.Build stage (
rust:1.99.1-slim, on the build machine's own platform)wasm32-unknown-unknowntarget; Trunk 0.21.14 pinned and checksum-verified asweb-deploy.ymlanddocs-preview.ymlpin it, plus the aarch64 checksum for arm64 build machines. The download steps are chained with&&, so a wrong checksum stops the build in any shell (older Podman ignoresSHELL).trunk build --release --locked --public-url ./, thencargo xtask web-pwa dist/web: the deploy's build without the analytics tag, with relative addresses as in the docs preview, so a copy also works under a path.target/and Trunk's tools.FROM --platform=$BUILDPLATFORM: the output is WebAssembly, so an image for another platform (or forlinux/amd64,linux/arm64at once) compiles it once, natively.Serve stage (
nginxinc/nginx-unprivileged:1.31.7-alpine-slim)HEALTHCHECKon/healthz, and OCI labels (the base image's own labels are replaced or cleared).packaging/docker/nginx.conf: the content-hashed.jsand.wasmare cachedimmutableand everything elseno-cache, so a new image reaches the app's new-version notice; gzip (also behind proxies), UTF-8 for text,application/manifest+jsonfor the manifest,nosniffand relative redirects. NoX-Frame-Options: the Python package's notebook and Qt widgets frame the app (?embed).Both base images are pinned by tag and digest; a
dockerentry in.github/dependabot.ymlkeeps them current.Docs: Self-host the web app with Docker in
docs/installation.md(build and run, HTTPS through a reverse proxy, serving under a path, ArcGIS sign-in on a self-hosted copy, images for other platforms), linked fromdocs/python.mdandpackaging/README.md; the self-hosting rows of the comparison pages;ROADMAP.md(Done) andGEOLIBRE_PARITY.md(still partial: no published image or runtime config).Testing
docker buildon arm64 (macOS) anddocker buildx build --platform linux/amd64 --load: both pass, and the amd64 build reuses the arm64 build stage (no emulated compile). A two-platformdocker buildx buildproduced one index withlinux/amd64andlinux/arm64, building the WebAssembly once.--read-only --tmpfs /tmpand the amd64 one under emulation: healthy; per-fileContent-Type,Cache-Control, gzip and redirects as described; 404 for unknown paths./and under/rustgis/behind a path-stripping proxy; the service worker registers and serves the app from its cache on reload; no console errors (screenshot in a comment below)./bin/sh.docker build --check: no warnings. hadolint 2.12: only DL3008; apt versions are unpinned on purpose (the base is pinned by digest, Debian drops superseded versions, and the build stage isn't shipped).cargo xtask ci: 2,437 passed, 0 failed, 16 ignored, layers ok;pre-commit run --all-files: passed;zensical build: no issues.Related issue
No issue: the roadmap item is marked (open). Lands A Dockerfile for self-hosting the web app (
ROADMAP.md› Self-contained, any time) and is part of Docker self-hosting image with runtime config inGEOLIBRE_PARITY.md.Possible follow-ups, not in this PR: a CI job that builds the image when the Dockerfile or the web build changes; publishing the image; runtime config.
Summary by CodeRabbit