Repository navigation
Axum: scaffold hardened production application Dockerfiles #398
Description
Part of #391.
Description
Give generated Axum applications a tested multi-stage Docker recipe. EdgeZero owns packaging templates and a disposable CI application; downstream repositories such as Trusted Server own the images they publish. No public EdgeZero application image or custom runtime base image is required.
Current implementation on audited main
At 98930917d96cb665c7255f36ca8bbd61fc7539e1, the Axum blueprint renders Cargo.toml, axum.toml, and main.rs but no container files. The generator renders workspace-root files separately, prefers local EdgeZero dependency paths, and does not generate Cargo.lock. See crates/edgezero-adapter-axum/src/cli.rs, crates/edgezero-cli/src/generator.rs, and crates/edgezero-cli/src/scaffold.rs.
The guide's rust:1.75 Docker sample is stale for the audited edition/toolchain and does not establish non-root execution or container-network binding. The separate build-tool container plan is not an application runtime image. PR #275 changes the HTTP/application contracts; do not package retired interfaces as the final production path.
Concrete consumer assumptions: Trusted Server
Use Trusted Server at 666953a0de0d as the consumer example, not as an instruction to pin new apps permanently to its current release:
- Its Cargo.toml uses EdgeZero Git release tags, currently v0.0.8, and commits Cargo.lock. Production apps should use an approved tag or commit plus their own committed lockfile, not paths into a developer's EdgeZero checkout. A dedicated fixture may stage local sources to test an unmerged change, but that is not the deployment recipe.
- Its rust-toolchain.toml pins Rust, and .tool-versions records Rust/Node. The image must use the consumer's declared toolchain; if declarations disagree, fail clearly rather than silently select one. For generated apps, retain the supported generated toolchain source and avoid a separately drifting Docker default.
- Its JS build script embeds bundles in Rust. Support a documented application-specific build stage: locked npm install and fresh JS build before Cargo. Do not add Node to every EdgeZero app or to the final runtime image.
- Build only the selected native application package/binary with
--release --locked, not the operator CLI or all provider targets. Required source manifests/assets must remain in the build context. - Config blobs, signing secrets, and mutable state are runtime inputs. The application owns validation/bootstrap and key rotation; the Dockerfile must not fabricate config, bake secrets, or mint keys on each startup.
The generator currently prefers local path dependencies and does not create Cargo.lock. Document or implement an explicit portable dependency choice. Resolve and commit the app lockfile as a deliberate preparation step; do not copy EdgeZero's lockfile, rewrite dependencies inside Docker, or make offline scaffolding unexpectedly fetch the network.
Proposed solution
- Establish a portable generated-app dependency/lockfile path using the consumer assumptions above. Test both a standalone consumer context and the deliberate repository fixture context; don't rely on an absolute developer path.
- Generate an Axum Dockerfile and matching context exclusions using the actual workspace/package names. Prefer workspace-root Dockerfile/.dockerignore when Axum is selected, with ordinary
docker build .; reuse current generator ownership rather than invent a general scaffold abstraction. - Use a pinned supported GNU/Linux Rust builder and compatible maintained Debian-slim runtime initially. Derive/validate Rust against the application's declared toolchain. Build the native application with
--release --locked, include required TLS trust/native libraries, and copy only runtime artifacts into the final stage. - Use a numeric non-root UID, exec-form startup, explicit 0.0.0.0/container port binding, a read-only root filesystem, and explicit writable state mounts. Do not change safe local-development bind defaults globally.
- Exclude local secrets/state, .git, and host build output from the Docker context where appropriate. Keep all required workspace manifests and build-time application assets. No runtime source tree, compiler, Node, operator credentials, or deployment-specific configuration.
- Prove the recipe with a disposable generated app. Keep commands and documentation in sync; generic Kubernetes/cloud operations belong to Axum: document Docker, Kubernetes, and managed-cloud deployments #400.
Delivery slices and dependencies
A. Packaging preparation, start now: dependency/lockfile/toolchain contract, template render checks, Docker build, HTTP port smoke, non-root/read-only execution, runtime TLS/native-library checks. This can land before PR #275 and the residual runtime tasks are complete, provided it is labeled packaging validation rather than production certification.
B. Production integration: re-audit and target the accepted PR #275 contracts; run lifecycle/readiness checks from #392 and config/storage checks from #396 in the image. Relevant buffered-body, streaming, outbound, and diagnostics checks come from #393/#394/#395/#397. #393 does not own general overload/deadline policy.
#399 applies the same recipe/tests to both approved architectures and supplies a downstream publishing workflow. A separate build-tool image, public EdgeZero demo, and Trusted Server's application implementation are not dependencies of slice A.
Done when
- A generated app with portable dependencies and its own committed Cargo.lock builds from its documented Docker context; no out-of-context developer checkout is needed.
- The selected Rust toolchain matches the consumer declaration; custom JS/assets can be built explicitly before Rust without making Node a framework-wide dependency.
- The image serves through its published port as non-root with a read-only root filesystem and dropped capabilities under documented mounts.
- Controlled tests prove required native libraries and outbound TLS trust work in the final image.
- Final-image/context checks exclude source/toolchains, .git, local secrets/state, and build-time credentials as documented.
- After runtime integration, required-config failure, mount permissions, durable restart, readiness, and bounded SIGTERM behavior pass through the actual image.
- Documentation distinguishes checks already proved by slice A from runtime acceptance still pending in slice B.
- No EdgeZero application image publication is needed to complete this issue.
Related work and boundaries
#161 covers generated workflows; #399 covers architecture CI/publishing guidance; #400 covers deployment guidance. Trusted Server #897 owns its self-hosted image/installer direction, with config/health/store issues in that repository. The existing EdgeZero build-cache container plan remains separate. #363 concerns provider sandbox reuse, not a required Docker base image.
Affected areas
Adapter -- Axum; CLI (new, build, deploy, dev); CI / Tooling; Documentation.
Implementation and relationship tracking
Packaging slice A: draft PR #401 on issue-398-docker-spec, based directly on main. It intentionally remains outside runtime stack #408 so packaging preparation can land independently. Slice B still consumes the accepted runtime contracts and final-image checks described above. This issue blocks #399's architecture/publishing acceptance and #400's runnable Docker guidance; GitHub Development now links it to PR #401, whose description includes Closes #398. The issue remains open now. Slice B acceptance remains outstanding and the PR remains draft.
Activity