From 9eb72536fe959270c728e8a10fd17f0ac1a68ccb Mon Sep 17 00:00:00 2001 From: Jeroen Rinzema Date: Sat, 8 Aug 2026 11:53:49 +0200 Subject: [PATCH 1/2] fix: stop shipping a second copy of the toolchain in every Railpack image The non-root layer ended with `chown -R 1000:1000 /mise`. /mise arrives from an earlier layer, so rewriting its ownership makes BuildKit record every file in it again: a byte-identical duplicate of the whole toolchain, in the layer users pull. On a stock create-next-app that is 194 MB on disk and 58.7 MB across the wire, for no change in content. It was never needed. /mise/installs is drwxr-xr-x root:root, so uid 1000 already has the read and execute it uses. What mise does want at runtime is somewhere to write, and it takes exactly two directories -- without them it prints `migrate: failed create_dir_all: /mise/migrations` on every start. Create those two instead of taking ownership of the tree. Measured on linux/arm64 against Railpack v0.35.0, uncompressed image size: create-next-app 1163 MB -> 969 MB Flask + gunicorn 407 MB -> 328 MB Both run as uid=1000(railpack) and serve 200. Verified node, npm, python and pip resolve through /mise/shims, that `mise exec` and `mise ls` work, and that no permission warning is left on either. --- src/image/mod.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/image/mod.rs b/src/image/mod.rs index c666b70..3474e16 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -25,12 +25,16 @@ const RAILPACK_FRONTEND: &str = "ghcr.io/railwayapp/railpack-frontend:v0.35.0"; const PROCESS_TIMEOUT: Duration = Duration::from_secs(60 * 60); const PROCESS_OUTPUT_DRAIN_TIMEOUT: Duration = Duration::from_secs(1); const NETWORK_TIMEOUT: Duration = Duration::from_secs(30 * 60); +// Never chown /mise here: it arrives from an earlier layer, so rewriting its +// ownership copies the entire toolchain into this one (194 MB for a Node app). +// Its contents are already world-readable and executable; mise only needs to +// write these two directories at runtime. const NON_ROOT_DOCKERFILE: &str = r#"FROM base RUN groupadd --gid 1000 railpack \ && useradd --uid 1000 --gid 1000 --home-dir /home/railpack --create-home --shell /bin/false railpack \ && if [ -d /root ]; then cp -a /root/. /home/railpack/; fi \ && chown -R 1000:1000 /home/railpack \ - && if [ -d /mise ]; then chown -R 1000:1000 /mise; fi + && if [ -d /mise ]; then install -d -o 1000 -g 1000 /mise/cache /mise/migrations; fi ENV HOME=/home/railpack USER 1000:1000 "#; From 811f0df71be5d781cc5c6cc2c7f6f0863be79a9e Mon Sep 17 00:00:00 2001 From: Jeroen Rinzema Date: Sat, 8 Aug 2026 21:30:30 +0200 Subject: [PATCH 2/2] style: stop commenting code that explains itself Leaves only the warning that has to survive: chowning /mise duplicates the toolchain, which is the mistake this fix exists to prevent being reintroduced. --- src/auth.rs | 4 ---- src/image/mod.rs | 2 -- 2 files changed, 6 deletions(-) diff --git a/src/auth.rs b/src/auth.rs index bb158aa..75dd532 100644 --- a/src/auth.rs +++ b/src/auth.rs @@ -133,10 +133,6 @@ struct AuthorizeAnnouncement<'a> { expires_in_seconds: u64, } -/// Writes the authorization URL to stdout before waiting for the callback. -/// -/// The authorization URL redirects to a loopback address, so it only completes -/// in a browser running on the same machine as the CLI. fn write_authorization_notice(authorize_url: &str, options: LoginOptions) -> Result<()> { let notice = authorization_notice(authorize_url, options)?; let stdout = io::stdout(); diff --git a/src/image/mod.rs b/src/image/mod.rs index 3474e16..4272ef5 100644 --- a/src/image/mod.rs +++ b/src/image/mod.rs @@ -27,8 +27,6 @@ const PROCESS_OUTPUT_DRAIN_TIMEOUT: Duration = Duration::from_secs(1); const NETWORK_TIMEOUT: Duration = Duration::from_secs(30 * 60); // Never chown /mise here: it arrives from an earlier layer, so rewriting its // ownership copies the entire toolchain into this one (194 MB for a Node app). -// Its contents are already world-readable and executable; mise only needs to -// write these two directories at runtime. const NON_ROOT_DOCKERFILE: &str = r#"FROM base RUN groupadd --gid 1000 railpack \ && useradd --uid 1000 --gid 1000 --home-dir /home/railpack --create-home --shell /bin/false railpack \