Skip to content

Optimize production Docker builds - #1503

Open
skyfallwastaken wants to merge 4 commits into
mainfrom
optimize-docker-build
Open

Optimize production Docker builds#1503
skyfallwastaken wants to merge 4 commits into
mainfrom
optimize-docker-build

Conversation

@skyfallwastaken

@skyfallwastaken skyfallwastaken commented Aug 1, 2026

Copy link
Copy Markdown
Member

Summary of the problem

Docker builds were very slow, and the images were rather large too!

Describe your changes

the gory details...
  • Split runtime packages, Ruby dependencies, JavaScript dependencies, Rails assets, and Blume into independent BuildKit stages so work runs concurrently.
  • Pin Bun 1.3.10 and copy it from the slim official image instead of running the network installer.
  • Exclude development and test gems from production; production now installs 147 gems rather than 186.
  • Keep Ruby and JavaScript dependencies out of application layers: build commands consume them through stage bind mounts, and the final image copies gems directly from the dependency stage.
  • Build Blume from only its actual inputs, preventing Rails-only edits from rebuilding docs.
  • Build Vite client and SSR outputs concurrently with independent cache directories. The Vite SSR bundle remains self-contained, so runtime node_modules is unnecessary.
  • Combine js_from_routes generation and Rails asset precompilation into one Rails boot.
  • Prepare runtime fonts, user, and Git configuration outside the critical asset path.
  • Copy writable runtime trees separately with UID/GID 1000 ownership rather than recursively chowning the completed image.
  • Remove unused production wget, unzip, and vim packages. Retain curl for Coolify healthchecks. Git, tar, SQLite, PostgreSQL, libvips, fontconfig, and Bun remain available.
  • Upgrade Ruby from 4.0.5 to the routine bugfix release 4.0.6 across production, worker, development, and .ruby-version.

Benchmarks

Measured on the same 8-vCPU, 15 GiB Amp orb with BuildKit v0.31.2 and overlayfs. Both builds exported compressed OCI archives, so wall time includes image compression/export.

  • Cold: BuildKit state physically deleted, daemon restarted, and --no-cache used.
  • Warm: daemon restarted to clear retained process memory while preserving the completed cold-build cache.
  • Baseline: merged main at 9913ab0.
Measurement Baseline Optimized Change
Cold build + OCI export 149.80 s 64.55 s -56.9%
Cold image construction before export 58.45 s
OCI export portion 6.10 s
Warm build + OCI export 3.38 s 2.00 s -40.8%
Compressed OCI archive 541 MiB 242 MiB -55.2%
Uncompressed layer file data 1.40 GiB 613 MiB -57.3%
Unpacked final rootfs 752 MiB 594 MiB -21.0%
BuildKit state filesystem 4.09 GiB 3.16 GiB -22.6%
buildctl du cache accounting 4.95 GB 3.76 GB -24.0%
Cold peak BuildKit cgroup memory 2.44 GiB 1.36 GiB -44.3%
Cold peak host-used memory 2.06 GiB 3.08 GiB +49.2%
Warm peak BuildKit cgroup memory 45.6 MiB 44.1 MiB -3.3%
Warm peak host-used memory 863 MiB 882 MiB +2.2%

The increased aggregate cold host memory is the intentional tradeoff for concurrent package, dependency, docs, client, and SSR work. BuildKit's own measured peak declined substantially and the total remains well below available CI memory.

Screenshots / Media

N/A

Copilot AI review requested due to automatic review settings August 1, 2026 16:21

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR optimizes the production Docker build to reduce cold build time and runtime image size by parallelizing dependency/asset stages and ensuring the SSR bundle no longer requires shipping node_modules in the final image.

Changes:

  • Refactors the Dockerfile into parallel Ruby/JS dependency stages and parallel Rails/Blume asset stages, then combines outputs into a slimmer runtime image.
  • Improves Docker build caching by excluding Blume-only inputs from the Rails asset compilation cache key.
  • Updates Vite SSR configuration to bundle dependencies (ssr.noExternal) so the SSR output is self-contained.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated 1 comment.

File Description
Dockerfile Splits dependency and asset compilation into parallel BuildKit stages; avoids copying build-only node_modules into the runtime image.
vite.config.ts Makes the SSR bundle self-contained by setting ssr.noExternal: true.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread Dockerfile Outdated
@skyfallwastaken

Copy link
Copy Markdown
Member Author

@greptile-apps review

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants