boringbuilder 0.1.0.alpha.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/AGENTS.md +38 -0
  3. data/CHANGELOG.md +13 -0
  4. data/LICENSE +21 -0
  5. data/README.md +131 -0
  6. data/SECURITY.md +25 -0
  7. data/SOUL.md +40 -0
  8. data/STYLE.md +33 -0
  9. data/docs/artifacts.md +106 -0
  10. data/docs/building.md +175 -0
  11. data/docs/caching.md +53 -0
  12. data/exe/boringbuilder +6 -0
  13. data/lib/boringbuilder/artifact.rb +87 -0
  14. data/lib/boringbuilder/boring_cache.rb +126 -0
  15. data/lib/boringbuilder/build_progress.rb +90 -0
  16. data/lib/boringbuilder/builder.rb +33 -0
  17. data/lib/boringbuilder/cli.rb +234 -0
  18. data/lib/boringbuilder/config_file.rb +40 -0
  19. data/lib/boringbuilder/configuration.rb +165 -0
  20. data/lib/boringbuilder/errors.rb +9 -0
  21. data/lib/boringbuilder/exporter.rb +144 -0
  22. data/lib/boringbuilder/exporters/asset.rb +7 -0
  23. data/lib/boringbuilder/exporters/boring_cache.rb +83 -0
  24. data/lib/boringbuilder/exporters/local.rb +27 -0
  25. data/lib/boringbuilder/exporters/receipt.rb +11 -0
  26. data/lib/boringbuilder/exporters/resolver.rb +57 -0
  27. data/lib/boringbuilder/exporters.rb +7 -0
  28. data/lib/boringbuilder/initializer.rb +180 -0
  29. data/lib/boringbuilder/mise.rb +88 -0
  30. data/lib/boringbuilder/pipeline.rb +156 -0
  31. data/lib/boringbuilder/project.rb +264 -0
  32. data/lib/boringbuilder/project_plan.rb +78 -0
  33. data/lib/boringbuilder/rails_build.rb +6 -0
  34. data/lib/boringbuilder/railtie.rb +9 -0
  35. data/lib/boringbuilder/result.rb +33 -0
  36. data/lib/boringbuilder/ruby_application.rb +142 -0
  37. data/lib/boringbuilder/ruby_build.rb +212 -0
  38. data/lib/boringbuilder/runtime.rb +89 -0
  39. data/lib/boringbuilder/tasks/boringbuilder.rake +40 -0
  40. data/lib/boringbuilder/version.rb +5 -0
  41. data/lib/boringbuilder.rb +45 -0
  42. metadata +108 -0
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA256:
3
+ metadata.gz: e20e908005e1a14c27c241b1540be57b73b7fa20f4a79513a22ce9535149fce4
4
+ data.tar.gz: e7b7c17cf76c2fcc852dd03765c1002bb8c32a993d3aac87b0b0561ac8e7a382
5
+ SHA512:
6
+ metadata.gz: 3950b539d458467b05b258bceb0d0e5a04f1c08fbb3dc1943db5f2750200c337694459090e5318334910be42f03495f260d134044e9a61fb4c6d4806bf4e5d9e
7
+ data.tar.gz: a7c8ee8d4b21afa7362c5f15846f943eff9ef1009c40b3860eae2802cddac826a25c6bd12e8c182468237378630cfbb0dbb53c2d9d5d6d6a020976ca37e20537
data/AGENTS.md ADDED
@@ -0,0 +1,38 @@
1
+ # Working on BoringBuilder
2
+
3
+ Read [SOUL.md](SOUL.md) and [STYLE.md](STYLE.md) before changing the product, code, or public language.
4
+
5
+ ## Product shape
6
+
7
+ - BoringBuilder builds any application. Rails is the first-class convention over a shared Mise-powered foundation.
8
+ - Hanami, Rack, and other Bundler applications share the built-in Ruby foundation.
9
+ - Other workloads use the same Mise foundation from an ordinary Ruby recipe backed by Dagger. Ruby is the extension
10
+ language, not an application requirement.
11
+ - Keep `boringbuilder build` conventional and `boringbuilder init` the small escape hatch.
12
+ - Do not introduce a Dockerfile frontend, a build YAML language, or provider-specific behavior into the build graph.
13
+ - Local and BoringCache caches use the same declared step paths. Local and BoringCache artifact exporters receive the
14
+ same normalized artifact.
15
+
16
+ ## Engineering habits
17
+
18
+ - Write clear Ruby against the current development toolchain while preserving the support contract in the gemspec
19
+ and CI.
20
+ - Keep the public surface small. Prefer a plain object or method over a new abstraction.
21
+ - Treat commands, paths, build contexts, archives, registries, and credentials as security boundaries.
22
+ - Never commit credentials, private source, local absolute paths, generated gems, dependency directories, or
23
+ application-specific integration details.
24
+ - Keep documentation human, direct, and useful to someone building their first production artifact.
25
+ - Add focused tests for behavior changes. Run `bin/ci` before committing.
26
+ - Run the relevant live Dagger build when changing the build, cache, runtime, or export graph.
27
+ - Inspect the built gem and the complete diff before release work.
28
+ - Treat these as defaults with reasons, not substitutes for repository invariants, security boundaries, or evidence.
29
+
30
+ ## Product checks
31
+
32
+ Before handing off a change, ask:
33
+
34
+ 1. Does the conventional Rails path remain simple?
35
+ 2. Can a non-Ruby application still express the same idea with a small recipe?
36
+ 3. Are local and shared caching behavior understandable?
37
+ 4. Can the result be exported or published without rebuilding it differently?
38
+ 5. Is the code pleasant for the next human—or agent—to read?
data/CHANGELOG.md ADDED
@@ -0,0 +1,13 @@
1
+ # Changelog
2
+
3
+ ## 0.1.0.alpha.1 - 2026-08-27
4
+
5
+ - Reintroduce BoringBuilder as a Ruby gem backed by Dagger Ruby.
6
+ - Add a convention-driven production Rails build.
7
+ - Support Apple Container and Docker Dagger runners.
8
+ - Support selective directory, tar, tar.zst, OCI, Docker, runtime-load, and registry-push outputs.
9
+ - Add local and BoringCache Artifact exporter adapters with automatic shared publication.
10
+ - Add cache-aware custom Dagger pipeline steps backed by local mounts and optional BoringCache persistence.
11
+ - Add a shared Mise toolchain foundation and `boringbuilder init` scaffolds for Ruby, Node, Rust, Go, and generic
12
+ Dagger pipelines.
13
+ - Add a Ruby API, CLI, Rails rake tasks, tests, CI, dependency auditing, and trusted RubyGems publishing.
data/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2025 BoringCache
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,131 @@
1
+ # BoringBuilder
2
+
3
+ [![CI](https://github.com/boringcache/builder/actions/workflows/ci.yml/badge.svg)](https://github.com/boringcache/builder/actions/workflows/ci.yml)
4
+
5
+ BoringBuilder turns application source into deployable artifacts and container images with
6
+ [Dagger](https://dagger.io). Rails is the first-class path, and every application can use the same
7
+ [Mise-powered](https://mise.jdx.dev/) toolchain from a small
8
+ [Dagger Ruby](https://github.com/boringcache/dagger_ruby) recipe. Local builds use Dagger's cache; shared builds use
9
+ [BoringCache](https://boringcache.com) without changing the recipe.
10
+
11
+ ![Generate and build a Node application with BoringBuilder](.github/assets/boringbuilder-demo.gif)
12
+
13
+ ## Getting started
14
+
15
+ BoringBuilder itself runs on Ruby, but the application being built does not need to. Dagger runs through
16
+ [Apple Container](https://docs.dagger.io/reference/container-runtimes/apple-container/) on supported Macs,
17
+ Docker on Linux or macOS, or an existing Dagger engine on a Linux VM.
18
+
19
+ Install the builder and run it from an application root:
20
+
21
+ ```sh
22
+ gem install boringbuilder --version "~> 0.1.0.alpha"
23
+ boringbuilder doctor
24
+ boringbuilder build
25
+ ```
26
+
27
+ Rails and other Ruby applications can keep the builder in their bundle:
28
+
29
+ ```sh
30
+ bundle add boringbuilder --version "~> 0.1.0.alpha"
31
+ bundle exec boringbuilder build
32
+ ```
33
+
34
+ The default build produces a zstd-compressed artifact with normalized archive metadata. Without shared credentials
35
+ it is written under `dist/`. BoringBuilder automatically:
36
+
37
+ 1. loads `config/boringbuilder.rb` when present;
38
+ 2. runs its custom Dagger Ruby recipe for any application; or
39
+ 3. uses the built-in Mise-powered Ruby pipeline when a `Gemfile` is present.
40
+
41
+ The built-in pipeline reads the Ruby toolchain from `mise.toml`, `.mise.toml`, `.tool-versions`, `.ruby-version`, or
42
+ `Gemfile`, then `Gemfile.lock`. It installs the selected toolchain with Mise inside the Dagger build. Generated
43
+ recipes use that same foundation for Node, Rust, Go, or any other tool available through Mise.
44
+
45
+ A root `Gemfile` identifies a Ruby application. Rails receives asset, Bootsnap, entrypoint, and `/up` health-check
46
+ conventions. Hanami, Rack, and `Procfile` applications receive production commands without pretending to be Rails.
47
+
48
+ Non-Ruby applications provide the few commands that make their build distinctive; BoringBuilder does not guess a
49
+ package manager or production output. Generate a detected Node, Rust, Go, Ruby, or generic starting point when
50
+ needed:
51
+
52
+ ```sh
53
+ boringbuilder init
54
+ boringbuilder build
55
+ ```
56
+
57
+ The generated Ruby stays small: `pipeline.mise` prepares the tools and source, while `pipeline.run` persists declared
58
+ dependency caches locally or through BoringCache. Builds print clean, numbered application steps while Dagger's
59
+ internal graph stays out of the way.
60
+
61
+ See [Building applications](docs/building.md) for the generated recipes and the complete pipeline API.
62
+
63
+ Command-line options remain explicit overrides:
64
+
65
+ ```sh
66
+ boringbuilder build --runtime apple
67
+ boringbuilder build --runtime docker --platform linux/arm64
68
+ ```
69
+
70
+ Rails applications can also use the included task:
71
+
72
+ ```sh
73
+ bin/rails boringbuilder:build
74
+ ```
75
+
76
+ ## Caching
77
+
78
+ Every build uses the selected Dagger engine's local cache. When BoringCache credentials and a workspace are present,
79
+ shared BoringCache entries replace the corresponding local mounts for Mise, Bundler, and declared custom pipeline
80
+ caches across fresh engines and CI runners.
81
+
82
+ See [Caching](docs/caching.md) for custom cached steps and restore-only builds.
83
+
84
+ ## Artifacts and images
85
+
86
+ The default `tar.zst` output is intended for atomic filesystem deployers such as BoringDeploy. Without shared
87
+ credentials it is written under `dist/`. With BoringCache save credentials it is published as a shared BoringCache
88
+ Artifact instead; pass `--output` when a local copy is also wanted.
89
+
90
+ The same build can produce or publish a container image:
91
+
92
+ ```sh
93
+ boringbuilder build --output dist/app.tar.zst
94
+ boringbuilder build --format oci --output dist/app.oci.tar
95
+ boringbuilder build --format docker --output dist/app.docker.tar
96
+ boringbuilder build --push ghcr.io/acme/app:latest
97
+ ```
98
+
99
+ Application-specific filesystem layouts live in ordinary Ruby:
100
+
101
+ ```ruby
102
+ # config/boringbuilder.rb
103
+ BoringBuilder.configure do |config|
104
+ config.artifact.directory("/app", at: "/opt/my-app/current")
105
+ end
106
+ ```
107
+
108
+ See [Artifacts and images](docs/artifacts.md) for directory selection, OCI and Docker archives, registry publishing,
109
+ and the Ruby API.
110
+
111
+ ## Documentation
112
+
113
+ - [Building applications](docs/building.md)
114
+ - [Artifacts and images](docs/artifacts.md)
115
+ - [Caching](docs/caching.md)
116
+ - [Project soul](SOUL.md)
117
+ - [Code and writing style](STYLE.md)
118
+ - [Agent guide](AGENTS.md)
119
+ - [Changelog](CHANGELOG.md)
120
+ - [Contributing](CONTRIBUTING.md)
121
+ - [Security policy](SECURITY.md)
122
+
123
+ ## Development
124
+
125
+ ```sh
126
+ mise install
127
+ bin/setup
128
+ bin/ci
129
+ ```
130
+
131
+ The checked-in toolchain is the development default. The gemspec and CI define the supported Ruby range.
data/SECURITY.md ADDED
@@ -0,0 +1,25 @@
1
+ # Security
2
+
3
+ ## Supported versions
4
+
5
+ Security fixes are applied to the latest published release. During the alpha, upgrade to the newest prerelease before
6
+ reporting a problem.
7
+
8
+ ## Report a vulnerability
9
+
10
+ Do not open a public issue for a suspected vulnerability. Email
11
+ [security@boringcache.com](mailto:security@boringcache.com) or use
12
+ [GitHub private vulnerability reporting](https://github.com/boringcache/builder/security/advisories/new).
13
+
14
+ Include the affected version, impact, reproduction steps, and any suggested mitigation. Do not include credentials,
15
+ private source code, or registry tokens unless the maintainers provide a secure channel.
16
+
17
+ ## Build boundaries
18
+
19
+ BoringBuilder sends the selected project context to a local Dagger Engine. `.gitignore` and the built-in source
20
+ exclusions reduce accidental context, but users remain responsible for excluding secrets from the project and image.
21
+ Registry authentication is handled by the selected runtime and Dagger; BoringBuilder does not accept credentials on
22
+ the command line.
23
+
24
+ `config/boringbuilder.rb` and files passed with `--config` are executable Ruby loaded on the host. Review recipes
25
+ before building source you do not trust.
data/SOUL.md ADDED
@@ -0,0 +1,40 @@
1
+ # The soul of BoringBuilder
2
+
3
+ Building an application should feel calm.
4
+
5
+ BoringBuilder exists for people who like the way Ruby and Rails make difficult work feel ordinary. They want a
6
+ production build without learning another configuration language, maintaining a pile of shell scripts, or giving up
7
+ control to a black box.
8
+
9
+ The goal is the ease of a buildpack with the clarity of a program. Dagger provides the build graph. Ruby makes that
10
+ graph readable. BoringBuilder adds strong conventions for the work most applications have in common.
11
+
12
+ ## What we believe
13
+
14
+ - `boringbuilder build` should be enough for a conventional Rails application.
15
+ - Rails is the first-class path, not the limit of the product.
16
+ - Any application can own a small Ruby recipe when its build needs are different.
17
+ - Mise should give every application the same predictable toolchain foundation without making developers think
18
+ about toolchain plumbing.
19
+ - A build should produce the same useful result as a filesystem artifact, OCI image, or Docker image.
20
+ - Caching should be automatic, close to the step that uses it, and easy to understand.
21
+ - Local builds should stay local. Shared BoringCache credentials should make the same steps portable across fresh
22
+ machines and CI runners.
23
+ - Secrets belong in secret channels, never command lines, images, archives, plans, or logs.
24
+
25
+ ## Our taste
26
+
27
+ We prefer a small amount of ordinary Ruby over a large amount of YAML. We choose conventions that solve a real
28
+ production build, then leave an honest extension point for applications that are different. We would rather add one
29
+ clear method than invent a framework around a possibility.
30
+
31
+ Errors should tell a tired person what happened and what to do next. Defaults should be safe. Generated files should
32
+ be short enough to understand in one sitting. Public documentation should sound like one human helping another.
33
+
34
+ ## The human reason
35
+
36
+ The human behind BoringBuilder was tired of build tooling asking people to choose between convenience and control.
37
+ They should not have to. A build can be simple, programmatic, fast, repeatable, and owned by the application at the
38
+ same time.
39
+
40
+ That is the standard: Rails-shaped happiness, buildpack-shaped ease, and Dagger-shaped control.
data/STYLE.md ADDED
@@ -0,0 +1,33 @@
1
+ # BoringBuilder style
2
+
3
+ Code should feel like a clear explanation of the build it performs. These are defaults with reasons, not laws. When
4
+ the code, security boundary, or user experience calls for something different, make that choice visible.
5
+
6
+ ## Ruby
7
+
8
+ - Write ordinary, readable Ruby. Prefer a small object or method over a framework made for a possibility.
9
+ - Arrange a class so its public story reads from top to bottom: class methods, public initialization and entry points,
10
+ supporting public methods, then private implementation.
11
+ - Keep methods close to invocation order when that makes the flow easier to follow.
12
+ - Prefer expanded conditionals when both branches matter. Use an early return near the start of a method when it
13
+ removes an exceptional or invalid case.
14
+ - Reserve a bang method for a meaningful counterpart without a bang.
15
+ - Follow RuboCop for mechanical style. The configuration is the executable baseline shared by editors and CI.
16
+
17
+ ## Product language
18
+
19
+ - Describe BoringBuilder as an application builder. Rails is the first-class convention, not the boundary.
20
+ - Lead with the command and the outcome. Explain Dagger, Mise, caching, or exporters only when they help someone make
21
+ the next decision.
22
+ - Write errors and documentation for a tired human. Be direct, specific, and calm.
23
+
24
+ ## Changes
25
+
26
+ - Find the closest existing shape before introducing a new one.
27
+ - Keep provider details behind cache and exporter adapters. Keep build recipes ordinary Dagger Ruby over the shared
28
+ Mise foundation.
29
+ - Surface invariants from CI, security policy, the gemspec, and public APIs; do not restate version promises in prose
30
+ where they can quietly go stale.
31
+ - Test the behavior at its narrowest boundary, then run `bin/ci`. Changes to the Dagger graph also need a live build.
32
+ - Before calling a change done, attack the diff: look for accidental complexity, secret exposure, private paths,
33
+ generated files, misleading examples, and behavior that was only mocked.
data/docs/artifacts.md ADDED
@@ -0,0 +1,106 @@
1
+ # Artifacts and images
2
+
3
+ BoringBuilder exports a directory, tar archive, reproducible zstd tar archive, OCI image archive, or Docker image
4
+ archive. It can also publish artifacts to BoringCache, load an image into the selected runtime, or publish an image
5
+ to a registry.
6
+
7
+ ## Export destinations
8
+
9
+ The default exporter is automatic:
10
+
11
+ - with a BoringCache save token and workspace, publish a shared BoringCache Artifact;
12
+ - otherwise, write the artifact under `dist/`;
13
+ - when `--output` is explicit, always keep that local copy as well.
14
+
15
+ Override the destination when needed:
16
+
17
+ ```sh
18
+ bundle exec boringbuilder build --exporter local
19
+ bundle exec boringbuilder build --exporter boringcache --artifact-name web-production
20
+ bundle exec boringbuilder build --json
21
+ ```
22
+
23
+ The BoringCache exporter uploads directly from the Dagger graph and returns a ready Artifact ID. It does not stage
24
+ the build through a host directory. Exporters are small Ruby adapters around the same normalized build asset, so a
25
+ future repository backend does not change the build pipeline or artifact model.
26
+
27
+ ## Filesystem artifacts
28
+
29
+ The default format is `tar.zst`. The built-in Ruby pipeline selects the application (`/rails` for Rails or `/app`
30
+ for other Ruby applications), `/usr/local`, and `/mise`. A custom pipeline selects the complete container root
31
+ unless the application narrows it.
32
+
33
+ ```sh
34
+ bundle exec boringbuilder build --format directory --output dist/rootfs
35
+ bundle exec boringbuilder build --format tar --output dist/app.tar
36
+ bundle exec boringbuilder build --format tar.zst --output dist/app.tar.zst
37
+ ```
38
+
39
+ A directory export replaces its destination. Archive exports replace the destination file.
40
+
41
+ Tar exports normalize ordering, timestamps, and ownership so an unchanged build produces the same archive.
42
+
43
+ Select or remap container paths with repeatable options:
44
+
45
+ ```sh
46
+ bundle exec boringbuilder build \
47
+ --path /rails=/opt/my-app/current \
48
+ --path /usr/local/bundle=/opt/my-app/current/vendor/bundle \
49
+ --file /usr/local/bin/mise=/usr/local/bin/mise \
50
+ --host-path ../release-metadata=/opt/my-app/release-metadata \
51
+ --format tar.zst \
52
+ --output dist/my-app-snapshot.tar.zst
53
+ ```
54
+
55
+ `--host-path` is explicit and requires a destination. It adds release metadata without widening the Docker build
56
+ context.
57
+
58
+ For a reusable layout, put the same choices in `config/boringbuilder.rb`:
59
+
60
+ ```ruby
61
+ BoringBuilder.configure do |config|
62
+ config.artifact.directory("/rails", at: "/opt/my-app/current")
63
+ config.artifact.directory("/usr/local/bundle", at: "/opt/my-app/current/vendor/bundle")
64
+ config.artifact.file("/usr/local/bin/mise")
65
+ config.artifact.host_path("../release-metadata", at: "/opt/my-app/release-metadata")
66
+ end
67
+ ```
68
+
69
+ ## Container images
70
+
71
+ Create portable image archives:
72
+
73
+ ```sh
74
+ bundle exec boringbuilder build --format oci --output dist/app.oci.tar
75
+ bundle exec boringbuilder build --format docker --output dist/app.docker.tar
76
+ ```
77
+
78
+ Load or publish the same built image:
79
+
80
+ ```sh
81
+ bundle exec boringbuilder build --load my-app:latest
82
+ bundle exec boringbuilder build --push ghcr.io/acme/my-app:latest
83
+ ```
84
+
85
+ Pass `--output` as well when both a local archive and a loaded or published image are required. Registry credentials
86
+ are owned by Dagger and the selected runtime; BoringBuilder does not accept them as command-line values.
87
+
88
+ ## Ruby API
89
+
90
+ Applications and deployment tools can use the same configuration directly:
91
+
92
+ ```ruby
93
+ result = BoringBuilder.build(
94
+ root: Rails.root,
95
+ runtime: :docker,
96
+ platform: "linux/arm64",
97
+ format: :tar_zst,
98
+ output: Rails.root.join("dist/app.tar.zst")
99
+ ) do |config|
100
+ config.artifact.directory("/rails", at: "/opt/my-app/current")
101
+ config.artifact.directory("/usr/local/bundle", at: "/opt/my-app/current/vendor/bundle")
102
+ end
103
+
104
+ puts result.path
105
+ puts result.artifact_id
106
+ ```
data/docs/building.md ADDED
@@ -0,0 +1,175 @@
1
+ # Building applications
2
+
3
+ BoringBuilder resolves one production build for an application, then executes it through Dagger. Run commands from
4
+ the application root unless a project directory is passed explicitly.
5
+
6
+ ```sh
7
+ bundle exec boringbuilder build
8
+ bundle exec boringbuilder build /path/to/application
9
+ ```
10
+
11
+ ## Build resolution
12
+
13
+ BoringBuilder chooses one pipeline:
14
+
15
+ 1. the custom Dagger Ruby pipeline in `config/boringbuilder.rb`, when configured;
16
+ 2. otherwise, the built-in Ruby pipeline.
17
+
18
+ An application recipe can also tune the built-in pipeline and define its artifact layout. Command-line options
19
+ override those settings for a one-off build.
20
+
21
+ ```sh
22
+ bundle exec boringbuilder build --config config/release.rb
23
+ bundle exec boringbuilder build --no-config
24
+ ```
25
+
26
+ Applications with `package.json` must provide a custom pipeline. BoringBuilder does not guess which JavaScript
27
+ runtime or package manager an application uses.
28
+
29
+ ## Generate a recipe
30
+
31
+ Run `init` when an application needs a custom pipeline or checked-in overrides:
32
+
33
+ ```sh
34
+ boringbuilder init
35
+ ```
36
+
37
+ BoringBuilder detects a conventional Ruby, Node, Rust, or Go project and writes `config/boringbuilder.rb`. An
38
+ unrecognized project receives a small generic Dagger recipe. Select a starting point explicitly when detection is
39
+ not enough:
40
+
41
+ ```sh
42
+ boringbuilder init --template node
43
+ boringbuilder init --template rust
44
+ boringbuilder init --template go
45
+ boringbuilder init --template generic
46
+ ```
47
+
48
+ The generated file is ordinary Ruby using BoringBuilder's Mise toolchain, Dagger containers, and cache/export
49
+ conventions. It is a starting point owned by the application, not generated state or a separate configuration
50
+ language. Recipes execute as Ruby on the host, so review them before building source you do not trust. `init` refuses
51
+ to replace an existing recipe; pass `--force` only when replacement is intentional.
52
+
53
+ ## Built-in Rails and Ruby pipeline
54
+
55
+ The built-in pipeline:
56
+
57
+ - reads the Ruby version from `mise.toml`, `.mise.toml`, `.tool-versions`, `.ruby-version`, `Gemfile`, or
58
+ `Gemfile.lock`;
59
+ - installs the toolchain with Mise and locked production gems with separate persistent caches;
60
+ - separates build packages from the runtime image;
61
+ - places the application under a non-root user; and
62
+ - leaves runtime secrets out of the image and exported artifact.
63
+
64
+ `mise.toml` (or `.mise.toml`) is read from its `[tools]` section using the conventional exact string form:
65
+
66
+ ```toml
67
+ [tools]
68
+ ruby = "4.0.6"
69
+ ```
70
+
71
+ The project file is copied into the build so its Ruby version and Mise settings are respected. The built-in path
72
+ installs Ruby only; custom pipelines can install any additional tools. When no Mise file is present, BoringBuilder
73
+ generates the minimal Ruby declaration from the other version sources.
74
+
75
+ ## Framework conventions
76
+
77
+ A root `Gemfile` selects the Ruby builder. Rails applications are detected by `config/application.rb` and receive
78
+ asset, Bootsnap, entrypoint, Thruster, port, and `/up` health-check conventions.
79
+
80
+ Hanami applications are detected by `config/app.rb` and use the production Puma configuration when present. Rack
81
+ applications are detected by `config.ru`. For any Ruby application, a `web:` entry in `Procfile` takes precedence.
82
+ Applications without a detected process still build a complete artifact; set a command in the recipe when producing
83
+ a directly runnable image:
84
+
85
+ ```ruby
86
+ BoringBuilder.configure do |config|
87
+ config.command = %w[bundle exec sidekiq]
88
+ config.port = 3000
89
+ end
90
+ ```
91
+
92
+ Add application-specific Debian packages without replacing the built-in pipeline:
93
+
94
+ ```ruby
95
+ BoringBuilder.configure do |config|
96
+ config.build_packages += %w[libsodium-dev]
97
+ config.runtime_packages += %w[libsodium23]
98
+ config.build_environment["REDIS_URL"] = "redis://127.0.0.1:6379/0"
99
+ end
100
+ ```
101
+
102
+ `build_environment` is for non-secret values needed while compiling. Use Dagger secrets in a custom pipeline for
103
+ sensitive build inputs.
104
+
105
+ Use a custom pipeline when the application needs JavaScript package installation, unusual system packages, a
106
+ different operating-system foundation, or is not a Ruby application.
107
+
108
+ ## Mise-powered application pipeline
109
+
110
+ For any application outside the built-in conventions, keep the build definition in Ruby. A recipe block receives a
111
+ small pipeline object and returns the final Dagger container:
112
+
113
+ ```ruby
114
+ # config/boringbuilder.rb
115
+ BoringBuilder.configure do |config|
116
+ config.artifact.directory("/app/dist", at: "/app")
117
+
118
+ config.pipeline do |pipeline|
119
+ app = pipeline.mise(tools: { node: "24" }, workdir: "/app")
120
+
121
+ app = pipeline.run(
122
+ app,
123
+ %w[npm ci],
124
+ cache: "npm-downloads",
125
+ at: "/root/.npm/_cacache",
126
+ workdir: "/app",
127
+ name: "Install dependencies"
128
+ )
129
+
130
+ pipeline.exec(app, %w[npm run build], name: "Build application")
131
+ end
132
+ end
133
+ ```
134
+
135
+ `pipeline.mise` prepares the requested tools and application source in a Dagger container. Project versions in
136
+ `mise.toml`, `.mise.toml`, or `.tool-versions` override recipe fallbacks, and `mise.lock` is respected when present.
137
+ Any tool in the [Mise registry](https://mise.jdx.dev/registry.html) can use the same shape.
138
+
139
+ `pipeline.run` persists the declared path in the local Dagger engine or through BoringCache when shared credentials
140
+ are present. `pipeline.exec` runs an ordinary named command. Both execute as real Dagger boundaries and show the
141
+ command's output beneath their numbered build step. Use a hash to cache several paths in one step. See
142
+ [Caching](caching.md) for both modes.
143
+
144
+ Both helpers return ordinary Dagger Ruby containers and use the same artifact and image exporters as the built-in
145
+ build. `pipeline.container` remains available when an application needs a different foundation.
146
+
147
+ ## Container runtime
148
+
149
+ `auto` prefers Apple Container on supported Apple silicon Macs and Docker elsewhere. A configured Dagger session can
150
+ point at an engine on a Linux VM without changing the build. Select a local runtime explicitly in CI and other
151
+ reproducible environments:
152
+
153
+ ```sh
154
+ bundle exec boringbuilder doctor --runtime apple
155
+ bundle exec boringbuilder build --runtime apple
156
+ bundle exec boringbuilder build --runtime docker
157
+ ```
158
+
159
+ Start Apple Container with `container system start`, or start the Docker daemon before selecting Docker. An existing
160
+ Dagger session or custom runner is used without probing the host runtime.
161
+
162
+ Dagger CLI and Engine versions must match the version required by Dagger Ruby. `boringbuilder doctor` checks that
163
+ contract before a build; pointing a different CLI at the expected Engine image is not an equivalent setup.
164
+
165
+ ## Build options
166
+
167
+ Target a deployment platform or inspect the resolved plan without building:
168
+
169
+ ```sh
170
+ bundle exec boringbuilder build --platform linux/arm64
171
+ bundle exec boringbuilder build --command "bundle exec puma -C config/puma.rb" --port 3000
172
+ bundle exec boringbuilder build --dry-run
173
+ ```
174
+
175
+ Run `bundle exec boringbuilder build --help` for the complete command reference.
data/docs/caching.md ADDED
@@ -0,0 +1,53 @@
1
+ # Caching
2
+
3
+ BoringBuilder has two cache paths. The build command and pipeline stay the same for both.
4
+
5
+ ## Local cache
6
+
7
+ Without BoringCache credentials, Dagger stores its content-addressed build graph plus BoringBuilder's named cache
8
+ mounts in the selected engine. Repeated builds on a persistent developer or CI engine reuse them automatically.
9
+
10
+ This cache belongs to that Dagger engine. A fresh ephemeral runner starts cold unless it connects to a persistent
11
+ Dagger engine or uses the shared mode below.
12
+
13
+ ## Shared cache with BoringCache
14
+
15
+ Shared cache turns on when BoringBuilder finds both:
16
+
17
+ - `BORINGCACHE_RESTORE_TOKEN` or `BORINGCACHE_SAVE_TOKEN`; and
18
+ - `BORINGCACHE_DEFAULT_WORKSPACE`, `BORINGCACHE_WORKSPACE`, or a project `.boringcache.toml` with its workspace.
19
+
20
+ The built-in Ruby pipeline then runs both `mise install` and `bundle install` through `boringcache run`. Tokens are
21
+ Dagger secrets and are not written to the resulting image or artifact. A restore token without a save token selects
22
+ read-only mode.
23
+
24
+ BoringCache persists those directories across fresh engines and runners. When shared caching is enabled it owns the
25
+ declared cache directories for that step; Dagger still reuses the rest of its local build graph. Selecting one owner
26
+ avoids overlapping mount and restore semantics. Restore, command execution, and save all happen inside the Dagger
27
+ container, with no host staging copy.
28
+
29
+ ## Custom application steps
30
+
31
+ Custom application recipes use the same cache primitive as the built-in Mise and Bundler steps:
32
+
33
+ ```ruby
34
+ app = pipeline.run(
35
+ app,
36
+ %w[bundle exec rake compile],
37
+ cache: {
38
+ "compile" => "/app/tmp/cache",
39
+ "downloads" => "/app/tmp/downloads"
40
+ },
41
+ name: "Compile application"
42
+ )
43
+ ```
44
+
45
+ Each hash key names a stable cache and each value is the directory visible to the command. With BoringCache
46
+ credentials those names and paths are restored and saved remotely. Without credentials, Dagger mounts named local
47
+ volumes at the same paths. Manual cache paths are removed after the command so dependency stores do not leak into
48
+ an image or artifact; copy any required build output elsewhere inside the cached command. Pass the single-cache
49
+ shorthand with `entry: "bundler"` (or another built-in/project entry) when BoringCache should use that existing
50
+ entry definition.
51
+
52
+ Cache persistence stays inside the Dagger step. Wrapping `boringbuilder build` with a host-side Docker or cache
53
+ command cannot see Dagger's named cache volumes, so BoringBuilder intentionally has no Dockerfile frontend.
data/exe/boringbuilder ADDED
@@ -0,0 +1,6 @@
1
+ #!/usr/bin/env ruby
2
+ # frozen_string_literal: true
3
+
4
+ require "boringbuilder"
5
+
6
+ exit BoringBuilder::CLI.start(ARGV)