build(release): vendor openssl in gravity_node so -p builds stay self-contained#717
Merged
Merged
Conversation
…-contained
`cargo build -p gravity_node` (e.g. via `make gravity_node`) scopes the
feature resolve to gravity_node only — without gravity_cli in the same
invocation, openssl-sys defaults to dynamic system linkage and the
resulting binary depends on libssl.so.3 / libcrypto.so.3, breaking
portability across hosts with mismatched libssl ABI (Debian 11 vs 12,
minor-version skew, etc).
Add openssl = { vendored } as a direct dep on gravity_node, mirroring
gravity_cli, so any cargo invocation produces a self-contained static
binary. Verified by building in a debian:bookworm-slim container with
libssl-dev removed (no ssl.h in /usr/include) — build succeeded and
the resulting binary runs cleanly on debian:bookworm-slim with no
libssl present.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
… add perl for vendored OpenSSL gravity_node no longer DT_NEEDEDs libssl.so.3 / libcrypto.so.3 / libudev / libzstd at runtime, so the runtime stages of both Dockerfiles can drop the explicit `libssl3 libudev1 libzstd1` apt installs. They will still come along transitively via curl / ca-certificates / dpkg / libsystemd0, so the image content is essentially unchanged — this is a "no longer declaring deps we don't actually consume" cleanup, not a size win. The builder stage of `Dockerfile` drops `libssl-dev` (vendored OpenSSL compiles its own copy from source) and adds `perl`, which is what the OpenSSL `Configure` script needs. Verified by building the binary in a `rust:1.93-bookworm` container with libssl-dev explicitly removed before the cargo build — produces a static-OpenSSL binary identical to the host build. End-to-end verified by booting gravity_node (built without libssl-dev) from the trimmed `Dockerfile.host-binary` image: full reth + consensus engine startup + outgoing HTTPS to telemetry.aptoslabs.com all work, using the vendored OpenSSL inside the binary. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
curl is not used by entrypoint.sh, docker-compose healthchecks, or any runtime path — it was only present as an operator-debug convenience documented in README.md. Removing it saves the libcurl4 / libgnutls30 / libkrb5-3 / libssh2-1 / libldap-2.5-0 transitive set: ~5MB off the final image (335MB → 330MB on the host-binary variant). Operators who curl into the container for ad-hoc RPC pokes can either `docker exec` from a curl-equipped host or install on demand. The README debug example still applies — just run it from the host pointing at the container's host-networked port. Verified end-to-end: built the host-binary image without curl, booted gravity_node node1 dev mode, and watched full reth + consensus engine startup plus successful outgoing HTTPS to telemetry.aptoslabs.com and sepolia.drpc.org from inside the container. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Longer support window (Ubuntu 24.04 LTS until 2029, +ESM to 2034 vs Debian 12 standard support to 2026-06), newer glibc (2.39 vs 2.36 → ~4 minor versions of headroom for future binary symbol upgrades), and fewer surprises for users on Ubuntu infra. Net image size: 330MB → 327MB (host-binary variant). Ubuntu's ca-certificates + jq + tini installs end up slightly leaner than Debian's despite the base image being ~3MB larger; the runtime stage trims the difference back. ca-certificates / jq / tini all present in Ubuntu repos under the same names. /usr/bin/tini and /usr/sbin/nologin paths match. Builder stage left on rust:1.93-slim-bookworm (Debian) — no upstream Ubuntu-based official rust image at the pinned 1.93 version, and the resulting binary works on either runtime base. Verified end-to-end: built the host-binary image on ubuntu:24.04, booted gravity_node node1 dev mode, watched full reth + P2P + consensus engine + RPC servers start, plus outgoing HTTPS to telemetry.aptoslabs.com and sepolia.drpc.org succeed via the vendored OpenSSL. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…runtime-host-binary target
The release pipeline already produces a native gravity_node binary on
the runner before any docker build. Compiling a second time inside
docker/gravity_node/Dockerfile's builder stage just to copy the result
into a runtime image is pure waste — 20-30 min on the slow path, plus
all the in-container cargo-git SSL flakiness regression/pfn_chain_stress
already had to work around with a separate Dockerfile.host-binary.
Restructure the main Dockerfile around two leaf targets sharing a
runtime-base stage:
- `runtime` (default): COPY from the in-container `builder` stage.
Same behaviour as before for plain `docker build .` — backwards
compatible.
- `runtime-host-binary`: COPY from a host build-context path supplied
via --build-arg HOST_BINARY. BuildKit's stage pruning means the
`builder` stage is genuinely skipped here, not just cached.
Usage in the release pipeline (no double compile):
cargo build --profile quick-release --bin gravity_node --bin gravity_cli
DOCKER_BUILDKIT=1 docker build \
--target runtime-host-binary \
--build-arg HOST_BINARY=target/quick-release/gravity_node \
-t gravity_node:$TAG \
-f docker/gravity_node/Dockerfile .
Delete Dockerfile.host-binary; update the 4 in-repo callers
(regression/pfn_chain_stress/run.sh, the corresponding docker-compose,
and the two regression READMEs) to use `--target runtime-host-binary`
against the unified Dockerfile.
Verified end-to-end:
- `docker build --target runtime-host-binary ...` → 205MB image,
node boots cleanly, HTTPS to telemetry/drpc works.
- `docker build ...` (default `runtime` target, full cargo build
inside the container) → 203MB image, same end-to-end behaviour.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the native cargo build that produces the GitHub-Release binaries, add a step that wraps the just-built target/quick-release/gravity_node into a docker image using docker/gravity_node/Dockerfile's `runtime-host-binary` target. BuildKit stage pruning means the `builder` stage is skipped entirely — no recompile after the native cargo step. Build runs on both PR dry-runs (validation only, no push) and tag pushes (followed by docker login + push to ghcr.io/<owner>/gravity_node:<tag>). Image is pushed to ghcr.io using the workflow's GITHUB_TOKEN (now with `packages: write` permission added at the workflow level). Owner name is lowercased for GHCR namespace rules. Runner deps doc-comment updated: libssl-dev removed from required build deps (the vendored OpenSSL change earlier in this PR makes it no longer needed), and docker added. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…uild The repo's .dockerignore excludes `target/` from the docker build context (standard Rust pattern — `target/` is too big to ship into every docker build). The new docker image step was passing `HOST_BINARY=target/quick-release/gravity_node` directly, which made the COPY fail at runtime: ERROR: "/target/quick-release/gravity_node" not found: not found Fix: copy the cargo-built binary into docker/gravity_node/bin/ first (a path that IS in the docker context — used by regression/pfn_chain_stress for the same reason) and point HOST_BINARY at the staged copy. Clean up the staged file after the build so the runner's working dir stays tidy across jobs. Verified locally: re-ran the exact step verbatim, docker build succeeded in <1s (BuildKit reused all cached layers), image is 203MB, boots cleanly with vendored-OpenSSL outbound HTTPS. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
nekomoto911
approved these changes
May 15, 2026
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.
Summary
cargo build -p gravity_node(e.g. viamake gravity_node) was producing a binary with dynamiclibssl.so.3+libcrypto.so.3deps, which fail to run on hosts whose libssl ABI differs from the build host (Debian 11 vs 12, libssl1.1 vs libssl3, minor-version skew).aptos-vault-client → native-tls, and resolver-v2 only sees thevendoredfeature when gravity_cli (which declaresopenssl = { vendored }) is part of the same cargo resolve. Scoping with-p gravity_nodeexcludes it, so openssl-sys falls back to dynamic system linkage.cargo build --bin gravity_node --bin gravity_cli) so the official artifact is fine, butmake gravity_nodeand any one-off-p gravity_nodebuild are brittle.openssl = { version = "0.10", features = ["vendored"] }directly onbin/gravity_node/Cargo.toml, mirroringbin/gravity_cli/Cargo.toml. openssl-sys now always builds OpenSSL from source and statically links it regardless of cargo invocation.Verification
cargo build --bin gravity_node --bin gravity_cli(CI release)cargo build --bin gravity_node(no-p)cargo build -p gravity_node(make gravity_node)Reproduced from-scratch in
rust:1.93-bookwormwithapt-get remove libssl-dev libcurl4-openssl-devfirst (nossl.hin/usr/include).cargo build --profile quick-release -p gravity_nodefinished in 3m39s, and the resulting binary runs cleanly ondebian:bookworm-slim(no libssl present) — confirms static OpenSSL 3.4.1 baked in (strings,nm,objdump -pall agree).End-to-end node startup verified inside both
debian:bookworm-slimandubuntu:24.04with no openssl packages installed: full reth init, RocksDB open, P2P networking up, JWT secret generated, consensus engine started, RPC HTTP/auth/IPC servers listening, plus successful outgoing HTTPS totelemetry.aptoslabs.comandsepolia.drpc.org(rustls + the vendored OpenSSL inside the binary).After this PR,
gravity_nodeonly DT_NEEDEDslibc.so.6 / ld-linux-x86-64.so.2 / libstdc++.so.6 / libgcc_s.so.1 / libm.so.6— all of which ship in any glibc-based base image (Debian 12 / Ubuntu 22.04+).Follow-on commits in this PR
build(docker): drop libssl-dev / libssl3 / libudev{,1} / libzstd{,1}, add perl for vendored OpenSSLSince gravity_node no longer DT_NEEDEDs libssl3/libudev1/libzstd1, the explicit apt installs of those packages in the runtime stage of
docker/gravity_node/Dockerfile{,.host-binary}are cleaned up. Builder stage dropslibssl-dev(replaced by vendored OpenSSL self-compile) and addsperl(needed by OpenSSL'sConfigurescript).Honesty caveat —
libssl3 / libudev1 / libzstd1are still in the runtime image as transitive dependencies ofca-certificates → openssl,dpkg / libsystemd0 / libapt-pkg6.0, andutil-linux. They're structural, not removable without droppingca-certificates(which we need for rustls cert-chain validation against external HTTPS endpoints — telemetry, drpc, GCP secret manager, etc.). This commit is about not declaring deps we don't actually consume — image content is essentially unchanged for the runtime stage.The builder-stage
libssl-dev → perlswap is a real cleanup (~few MB of dev headers gone).build(docker): drop curl from runtime imagecurlwas only present for operator-side ad-hoc RPC poking (per the README); nothing in the runtime path uses it. Removing it does shrink the image —libcurl4 / libgnutls30 / libkrb5-3 / libssh2-1 / libldap-2.5-0all come along withcurland now go away.build(docker): switch runtime base from debian:12-slim to ubuntu:24.04Both Dockerfile leaves switch their runtime stage from
debian:12-slimtoubuntu:24.04— longer support window (LTS to 2029 + ESM to 2034 vs Debian 12 to 2026-06 + ELTS to 2031), more glibc headroom (2.39 vs 2.36), comparable image size. Builder stage stays onrust:1.93-slim-bookworm(no upstream Ubuntu-basedrust:1.93image).build(docker): consolidate Dockerfile.host-binary into Dockerfile as runtime-host-binary targetRestructured the main Dockerfile around two leaf targets sharing a
runtime-basestage:runtime(default) —COPY --from=builder .... Runs cargo build inside the container. Behaviour unchanged for plaindocker build .; backwards compatible.runtime-host-binary—COPY ${HOST_BINARY} ...from the host build context. BuildKit's stage pruning means thebuilderstage is genuinely skipped — no cargo runs, ~12s build vs 20-30 min from-source. If the host binary is missing the build fails fast at the COPY step.Dockerfile.host-binaryis deleted; the 4 in-repo callers —regression/pfn_chain_stress/run.sh, the matching docker-compose, and the two regression READMEs — are updated to point at the unified Dockerfile with--target runtime-host-binary.ci(release): build + push gravity_node docker image to ghcr.ioAfter the native cargo build that produces the GitHub-Release binaries, the workflow now also builds a docker image via
--target runtime-host-binary, then (on tag pushes only) pushes it toghcr.io/<owner-lowercased>/gravity_node:<tag>. Stage pruning means no recompile after the native cargo step — the binary the runner just produced is the same one that ends up in the image. PR dry-runs build the image but don't push it.permissions: packages: writeadded so the defaultGITHUB_TOKENcan push to GHCR.libssl-devremoved (no longer needed thanks to the vendored OpenSSL change),dockeradded.Test plan
All verified locally:
cargo build -p gravity_nodeproduces a binary with nolibssl/libcryptoinlddcargo build --bin gravity_node --bin gravity_cli(release CI command) still works, both binaries free of libssl depscargo build -p gravity_node --features randomness_disabledresolves and buildsgravity_nodefrom-scratch in arust:1.93-bookwormcontainer withlibssl-devremoved and zerossl.hin/usr/include— succeededdocker build -f docker/gravity_node/Dockerfile .(defaultruntimetarget, full multi-stage cargo build inside the container) — succeeded, 203MB image, boots cleanly with vendored-OpenSSL outbound HTTPSdocker build --target runtime-host-binary --build-arg HOST_BINARY=... -f docker/gravity_node/Dockerfile .— succeeded in ~12s, 205MB image, boots cleanly, BuildKit confirmsbuilderstage was pruned (no cargo build ran)gravity_node node --devend-to-end inside bothdebian:bookworm-slimandubuntu:24.04runtime images with no openssl packages installedcurlis not referenced byentrypoint.sh,docker-compose*.yaml, or any runtime path in this repotinilives at/usr/bin/tiniandnologinat/usr/sbin/nologinonubuntu:24.04Will be exercised by CI on this PR (release.yml dry-run runs on PRs that touch the file, including this one):
--target runtime-host-binarydocker build step works on the runner's docker versionGITHUB_TOKENsucceeds anddocker pushtoghcr.io/<owner>/gravity_node:<tag>worksGenuinely external — reviewer needs to confirm:
curlbeing inside the containerdocker/gravity_node/Dockerfile.host-binary(the 4 in-repo references are updated in this PR)ghcr.io/<owner>/gravity_nodeis the right image name (the workflow uses${GITHUB_REPOSITORY_OWNER}lowercased +gravity_node; if you want a different namespace, this needs adjusting)🤖 Generated with Claude Code