Skip to content
Open
Changes from 5 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
226 changes: 226 additions & 0 deletions text/0133-explicit-layer-order.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
# Meta
[meta]: #meta
- Name: Explicit layer ordering
- Start Date: 2025-01-13
- Author(s): [@schneems](https://github.com/schneems)
- Status: Draft <!-- Acceptable values: Draft, Approved, On Hold, Superseded -->
- RFC Pull Request: (leave blank)
- CNB Pull Request: (leave blank)
- CNB Issue: (leave blank)
- Supersedes: (put "N/A" unless this replaces an existing RFC, then link to that RFC)

# Summary
[summary]: #summary

Allow buildpack authors to explicitly define a layer order.

# Definitions
[definitions]: #definitions

It's worth noting the current behavior. [Buildpacks](https://github.com/buildpacks/spec/blob/main/buildpack.md) produce layers on disk. These layers are loaded in order to produce a runnable image by the [platform's lifecycle](https://github.com/buildpacks/spec/blob/main/platform.md). The order in which the layers are loaded can have differing effects on the final image outcome. For example, when a layer prepends a value to the `PATH` environment variable, the last layer to be loaded will "win" if multiple layers prepend a path with the same executable name.
Copy link
Member

Choose a reason for hiding this comment

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

AFAIK the current implementation https://github.com/buildpacks/lifecycle/blob/2c0f9bd6cda7a25f5510b1b0c86e8a21273d0890/phase/exporter.go#L42 which exports the layers in order of the list of Buildpacks provided to the exporter. So the order of buildpack layers is explicit. As you point out below, this RFC is specifying an explicit order for the layers provided by a single buildpack.

Copy link
Author

Choose a reason for hiding this comment

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

Trying to respond to all comments. I'm not sure what you're trying to communicate here.

So the order of buildpack layers is explicit.

I think you might be responding to my wording choice in the RFC title "Explicit". From the platform perspective the order is specified/defined i.e. it's deterministic and consistent. I was thinking of the zen of python when I wrote it i.e. "Explicit is better than implicit." But implicit isn't quite the right word for the current behavior. It's more that it's ambiguous whether the effect of layer ordering on the name was intentional or not.

Maybe the title should be changed to "Decouple layer ordering from layer name with an explicit interface"? I think that's more precise, I updated the title of the PR.


Today's behavior is documented [in the buildpack spec](https://github.com/buildpacks/spec/blob/f3fd7555c8320a9da8101f52e6e952e9679fa150/buildpack.md#layer-paths):

> - The lifecycle MUST order all `<layer>` paths to reflect the reversed order of the buildpack group.
> - The lifecycle MUST order all `<layer>` paths provided by a given buildpack alphabetically ascending.

While multiple buildpacks have their execution order preserved implicitly, the layer ordering within a buildpack depends on the layer's name. So changing the name of the layers `gems` and `ruby` to something like `binruby` and `user_gems` would have the effect that executables such as `rake` (which ships with ruby) might now be loaded from a different layer path because the name has a different alphanumeric ordering.

The ordering of layers is important, and the current ordering mechanism (alphanumeric layer names) is not intuitive.

Layers need to be loaded in several locations:

- At "build" time, multiple buildpacks can run simultaneously. If a layer is created with `build=true` in its TOML, it will be loaded and visible to buildpacks that run after it.
- At "launch" time. When an image is finalized, if a layer is created with `launch=true`, it is TOML and will be loaded when the image is launched (such as `docker run`).
- Inside of the same buildpack. A large buildpack may generate several layers that depend on each other. For example the `heroku/ruby` buildpack first downloads a ruby executable and places it in a `ruby` layer. It then executes code such as `gem install` and `bundle install` that depend on that ruby executable. It's a buildpack author's responsibility to make sure any environment variable modifications needed at `build` or `launch` time are also tracked and re-exported (or passed) to code running in the same buildpack.
Copy link
Member

Choose a reason for hiding this comment

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

Would it be sufficient to make buildpack authors aware of the "internal buildpack" alphanumeric names? That way the buildpack authors could choose ye-olde-school 01layer and 02layer as names like rc.d files? By making buildpacks authors aware of the ordering could we avoid this RFC change which places a burden of understanding on all buildpack authors?

Copy link
Author

Choose a reason for hiding this comment

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

This is a small bit of text that makes that solution not possible for all scenarios:

  • The current ordering mechanism requires prefixing a directory to change order. Some binaries cannot be relocated without being rebuilt.

Basically, some binaries are not relocatable so what you're suggesting isn't possible for every buildpack author.

From the ergonomics side: I experimented with tooling to auto-re-order layers as they're written heroku/buildpacks-ruby#384. The result is much more complex than I would have guessed before implementation. I think this is approach is suboptimal for buildpacks written in languages like Rust or Go and is outside the reach for a buildpacks written in bash.

Copy link
Member

Choose a reason for hiding this comment

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

It's a buildpack author's responsibility to make sure any environment variable modifications needed at build or launch time are also tracked and re-exported (or passed) to code running in the same buildpack.

Could you elaborate on this part? I don't quite follow

Copy link
Author

Choose a reason for hiding this comment

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

Sure! Here's an explicit example, it's a bit verbose, but I hope it illustrates what I'm talking about:

Copy link
Author

@schneems schneems Mar 6, 2025

Choose a reason for hiding this comment

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

Possibly to add: If my original wording was unclear and you were able to follow my example: It might be helpful to have you re-explain this concept in your own words, that wording might make sense to a newcomer (I'm pretty close to this specific concept already so it's hard for me to spot the gaps in my language).

Copy link
Member

Choose a reason for hiding this comment

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

Thank you for the example. I guess if I were to restate it I would say, that the lifecycle does some special magic to prepare the environment for each buildpack, and buildpacks that both write and read environment variables to accomplish their work need to replicate the special magic of the lifecycle. Having an explicit ordering makes this all clearer because we can expect lifecycle(build), lifecycle(launch), and buildpack env setting to work "the same" if a buildpack creates layers in the order in which it expects them to be loaded.

Copy link
Member

Choose a reason for hiding this comment

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

Not sure if my language is any clearer here, but it makes sense in my head


The "inside of the same buildpack" relies on the order of buildpack execution to load environment modifications, while build and launch rely on the layer's name. The more layers added to a buildpack, the less likely the three ordering locations will match exactly.

# Motivation
[motivation]: #motivation

## Problem statement

- Today a single buildpack can write multiple layers.
Copy link
Member

Choose a reason for hiding this comment

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

Or it can write multiple slices. Slices allow explicit ordering through a list of glob patterns (https://buildpacks.io/docs/for-buildpack-authors/how-to/write-buildpacks/create-slice-layers/)

Copy link
Member

@natalieparellano natalieparellano Feb 24, 2025

Choose a reason for hiding this comment

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

But that's only for the application directory, no? (Also, ordering of layers within the image config is different from the order in which the lifecycle/launcher processes env files ...although it's interesting to think about what could happen if we combined these concepts)

- For launch or the next buildpack in build, each layer is evaluated in alphabetic order via the spec.
Copy link
Member

Choose a reason for hiding this comment

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

Is the following more precise?

Suggested change
- For launch or the next buildpack in build, each layer is evaluated in alphabetic order via the spec.
- The collection of layers provided by a buildpack is ordered by the directory order of the filesystem on which they are located

Copy link
Author

Choose a reason for hiding this comment

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

I don't think that's (strictly) correct. The lifecycle's ordering could diverge from the order of disks on the filesystem based on how "alphabetic order" is implemented.

The lifecycle/platform spec says

The lifecycle SHALL apply buildpack-provided modifications to the environment as outlined in the Buildpack Interface Specification.

From that link it says:

The lifecycle MUST order all paths provided by a given buildpack alphabetically ascending.

There's no "order of filesystem," in the spec. All ordering is done by the lifecycle, though I do understand that wording was intended to mimic what someone would view as a natural order of files on disk. Perhaps some wording to that effect might help the reader?

- This order can differ from the order that layers were written by the buildpack which is surprising and can result in difficult to debug problem.
Copy link
Member

Choose a reason for hiding this comment

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

Could you provide a concrete example? I understand the problem that layer A of buildpack X could provide /bin/foo and layer B of buildpack X could provide /bin/foo and whichever alias of that command is available at runtime depends on the order of export of the layers of buildpack X. I'm wondering in what concrete situations this has been a problem?

Copy link
Contributor

Choose a reason for hiding this comment

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

I'm wondering in what concrete situations this has been a problem?

In Richard's case, he encountered this issue with Ruby apps (iirc due to the Ruby app pattern of having bin-scripts in the app source?).

For me, I ran into this separately with Python. This issue affected our CNB since we need to use a venv (virtual environment), and venvs contain a bin/python script that must shadow the actual python binary on PATH in order for the venv to work as expected.

In an earlier CNB design (prior to switching to venvs), I had the Python binary installed in a layer named python and the app dependencies installed into a layer named dependencies (both layers in the same CNB; we've mostly avoided using micro-buildpacks for a number of reasons). I had to change the layer name to venv to make the layer envs ordered as expected, when I much preferred the naming dependencies.

Whilst I could have switched to eg 01_python and 02_dependencies, this:
(a) would cause all cached layers to be invalidated (both for that specific time, and any other time I want to add layers, if it shuffles the numbering)
(b) means things like app stack traces now expose ~messy implementation details
(c) could break customer apps if they had become dependent on certain layers having certain paths (which ideally they wouldn't, but ...)

I also think there is a possible CNB maintainer discoverability issue - whilst the existing spec does state the layer ordering aspect, it may be easy for someone to refactor a CNB and not realise the impact it had, vs an explicit ordering in the layer config.

- The current ordering mechanism requires prefixing a directory to change order. Some binaries cannot be relocated without being rebuilt.
Copy link
Member

Choose a reason for hiding this comment

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

I don't yet understand this. AFAIK we're saying that a binary at $CNB_LAYERS_DIR/<layer>/bin/foo can't be written to $CNB_LAYERS_DIR/01<layer>/bin/foo?

Copy link
Member

@hone hone Feb 21, 2025

Choose a reason for hiding this comment

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

@AidanDelaney This is the most compelling argument for me. I think you can mitigate with potentially better documentation/awareness for everything else. A concrete example here is that when compiling binaries, you often (sometimes for performance reasons) need to pass --prefix to the configure script. This hardcodes the path in binary itself. If the buildpack author needs to change the order via naming, this prefix configuration will be invalid which may cause the binary to be unable to run. This would mean the buildpack author would need to recompile any binaries in that path.

Copy link
Author

Choose a reason for hiding this comment

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

Also commented here #322 (comment)

- We should introduce a mechanism that allows buildpack authors to ensure that build, launch, and "inside of the same buildpack" have the same order of environment variable modification. This mechanism should allow non-relocatable binaries to be re-ordered.

> - Why should we do this?

To improve the correctness and reliability of buildpacks.

> - What use cases does it support?

- Re-ordering layers without having to move files on disk.
Copy link
Member

Choose a reason for hiding this comment

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

I don't yet understand why we need to re-order layers by moving files on disk? For example, the packteo php-composer buildpack creates 3 layers (https://github.com/paketo-buildpacks/php-composer/blob/428c4ce4577621703065c5f5a4f5e99a7c7b4751/packages/contributor.go#L80). If the buildpack wants to explicitly order these layers then it can, as you point out, name them 01composer, 02php-composer-packages and 03php-composer-cache?

Copy link
Author

Choose a reason for hiding this comment

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

I don't yet understand why we need to re-order layers by moving files on disk?

You suggested prefixing layer names with numbers:

A consequence of that is, you need to move files on disk. For example:

  • A buildpack has two layers 01_expensive_compiled_binary and 02_results_of_command

People deploy with this buildpack. The buildpack author then realizes they need to add another layer and it needs to come first

  • The buildpack now has three layers 01_first, 02_expensive_compiled_binary and 03_results_of_command.

If you want to preserve the contents of the cache of expensive_compiled_binary and results_of_command from prior deploys (which, lets say I do) then the only way to implement the prefix layer name ordering is by moving files on disk from 01_expensive_compiled_binary (from the first deploy) to 02_expensive_compiled_binary (to this current deploy).

Basically, I'm using "move files on disk" as a shorhand to reference the alternative you're suggesting.


> - What is the expected outcome?

Buildpack ordering will be more consistent for those that use this feature. The behavior will be less surprising to buildpack authors and users. Less time will be spent creatively renaming layers to achieve the desired ordering.

# What it is
[what-it-is]: #what-it-is

Allow buildpack authors to document the order in which layers were created by adding a value of `load_order` to a layer's TOML file as it is written.

For example:

```
build = true
launch = true
load-order = 1
```

# How it Works
[how-it-works]: #how-it-works

Add an optional positive numeric field to `<buildpack-layer-dir>/<layer-name>.toml` with a key `load_order`. Buildpack authors start with a number such as `1` and then increment it for every layer they write a new layer. The lifecycle reads these numbers and orders the written layers in the same ascending order. This way, `1` will be evaluated before `2`.

Any layers with a duplicate `load_order` will be evaluated in alphabetic order (the same behavior as today). Any layers without an explicit `load_order` will default to a value of `0`. This default means that any explicitly ordered layers will have their `PATH` modifications prepended after the default layers, which has the effect of taking precedence.

The section should return to the examples from the previous section, and explain more fully how the detailed proposal makes those examples work.

# Migration
[migration]: #migration

This change is a backward-compatible addition. Existing buildpacks will continue to function as they do today. Buildpack authors can begin using this scheme as soon as it is introduced and implemented. It will be a "major" feature addition as buildpacks that implement this feature cannot revert to an older API version that does not have this optional key without error.

# Drawbacks
[drawbacks]: #drawbacks

While TOML supports integers, the current implementation details of the CNB project mean that it is converted to JSON and then back to TOML which loses type information https://github.com/buildpacks/lifecycle/issues/884. This issue may make comparing values for equivalence harder. This behavior prevents me from strongly suggesting that the `load_order` key should be a TOML Integer type.

# Alternatives
[alternatives]: #alternatives
Copy link
Contributor

Choose a reason for hiding this comment

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

I'd like to see an alternative listed

Alternative: Order file in each layer

The individual buildpack author can emit <layer-dir>/CNB_LAUNCH_LAYER_ORDER_INDEX with a numeric value. From an implementation perspective, I think I like it better than launch.toml or a aggregate file at the buildpack directory. Being a file on disk means it doesn't have to be handled in intermediate files or show up in OCI image labels.

I don't know what implications it has on a buildpack author and if there would be any concerns around re-ordering layers and cache busting. I'd love for you weigh in @schneems

Copy link
Author

Choose a reason for hiding this comment

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

To riff on that request: I would propose $CNB_LAYERS_DIR/<layer>.order and have the value be a positive integer between 0-u64::Max (inclusive) with allowance for whitespace on either side of the number. This fits with some other use cases https://github.com/buildpacks/spec/blob/main/buildpack.md#build.

For my use case it would be $ cat /layers/bundler.order # => 42. I like that this distinguishes that things put inside of the layer dir (i.e. /layers/bundler/<contents> are for the benefit of the application versus metadata and other configuration are for the interface.

Could optionally do <name>.order.integer or <name>.load-order.integer. Though it seems a little odd to write a whole file for only one value.

I don't know what implications it has on a buildpack author and if there would be any concerns around re-ordering layers and cache busting. I'd love for you weigh in @schneems

I think that when the layer is restored, it wouldn't restore the order information, in the same way that cache = true is not persisted on cache load from the toml. From a library author, it's two file writes instead of one, for the platform implementer, it's two file reads instead of one.

I'm pondering on which interface is easier or harder to mess up. I.e. if someone does $ echo 2 >> /layers/wronglayername.load-order.integer or $ echo 2 /layers/<correctlayername>/load-order.integer (by accident) it would be easy to detect if the "wronglayername" doesn't exist, but hard if it does (if the order is input into the incorrect layer name). It would be hard to detect a mis-use like <correctlayername>/load-order.integer as CNB would think it's simply a file they want in that directory.

The last thing to consider is discovery of the buildpack author. If the examples show <layer-name>.toml with a load-order= in it, then It will be easy to see. If it's in a completely different file, I'm not sure we'll bother to update the examples. In general, I like my interfaces to not require me to remember to do two things (even if in this case, I still have to remember to add the load order to the file even if it's toml).

One possible modification (to any scheme) would be to make an order required i.e. error if load-order=0 is not found then fail to build or at least emit a warning. That seems invasive, maybe we wait to see if people get tripped up by forgetting it and consider adding a warning/error later.

Copy link
Contributor

Choose a reason for hiding this comment

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

I was actually suggesting that <layer-dir>/CNB_LAUNCH_LAYER_ORDER_INDEX as layers/bundler/CNB_LAUNCH_LAYER_ORDER_INDEX. I wasn't clear enough in my comment, sorry.

Your comment above mentions you like the inside/outside layer distinction. What is your opinion of per-layer file representation? I like that it travels with the layer and it doesn't bloat the metadata of the image or have the buildpack author having to do 2 things. A cat echo 2 > /layers/bundler/CNB_LOAD_ORDER is a pretty simple interface that doesn't require the layer name matching or anything like that.

Copy link
Contributor

Choose a reason for hiding this comment

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

@schneems what do you think about ^?

I'm not a heavy -1 on <layer>.toml but it ends up places I don't like (Image Label, config/metadata.toml). I quite like the simplicity of each directory having something in it that we can use for ordering at launch time. Having order stored outside the directory requires a list of ordered layers in a single location which feels more complicated and error prone (casing of dir names, length of dir names in toml array, what to do with items that don't have a match, etc).

Copy link
Member

Choose a reason for hiding this comment

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

I think that when the layer is restored, it wouldn't restore the order information, in the same way that cache = true is not persisted on cache load from the toml.

Is this the desired behavior?

Copy link
Member

Choose a reason for hiding this comment

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

I'm not a heavy -1 on .toml but it ends up places I don't like (Image Label, config/metadata.toml)

I agree, but this seems less bad to me than co-mingling "application stuff" (layer contents) with "lifecycle stuff" (layer order).

Copy link
Author

Choose a reason for hiding this comment

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

I think that when the layer is restored, it wouldn't restore the order information, in the same way that cache = true is not persisted on cache load from the toml.
Is this the desired behavior?

I think so. Otherwise it would be weird if someone remembered to write the cache/launch/build state, but forgot to update the order number and suddenly you've got something in an unexpected order.

I guess technically in that scenario (talking through it) you could make the argument that erasing the order would also make it default to 0 which could be considered "unexpected." I would guess that it's more expected that "not touching layer numbering" == "layer is now ordered as zero". As opposed to "not touching layer numbering" == "inherit from last known layer numbering." Basically: I think it's easier to reason about if the options are "Explicit or default to zero" rather than "explicit or default to inheritance." I'm open to a counter example though.

Copy link
Author

Choose a reason for hiding this comment

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

@jabrown85 I added a section with this alternative, can you review and resolve this conversation? b4f1f91


### Prefix naming (do-nothing with a workaround)

The individual buildpack author can implement this same behavior by changing their layer names, for example, changing `gems` to `0001_gems`, etc. This workaround is known as "prefix naming" and has a few drawbacks:

- When using prefix naming, if cached layers are re-ordered, then either their cache needs to be invalidated or moved. For example, if a layer is added before a `gems` layer, it would become `0002_gems`, but if the code tried to load `0002_gems.toml` and its associated layer data from a prior run, it wouldn't exist. This caching behavior means buildpack code must rename and move toml files and layer directories.
- Some layers may contain binaries that cannot be moved. These binaries contain absolute paths based on the system from where they were compiled, so moving them on disk can result in breakages. In an ideal world, such binaries would not exist, but it's an unrealistic demand to expect application owners to rely on only binaries that are relocatable.
- Debugging instructions or output may vary between applications. For example, one tutorial might say run `/layers/heroku_ruby/0003_gems/bin/rake -v` while the user's application has that layer in a different order. While this is a minor problem for experienced developers who can `ls` that directory and might understand that `0004_gems` is effectively the same as `0003_gems`, it will trip up newer developers.
- These values show up to the user and look odd/off. For example, this output comes from the `heroku/ruby` buildpack:

```
BUNDLE_BIN="/layers/heroku_ruby/0003_gems/bin" \
BUNDLE_CLEAN="1" BUNDLE_DEPLOYMENT="1" \
BUNDLE_GEMFILE="/workspace/Gemfile" \
BUNDLE_PATH="/layers/heroku_ruby/0003_gems" \
BUNDLE_WITHOUT="development:test"
bundle install`
```

This aesthetic issue is not the most severe concern but is worth noting.

**Overall**: These issues make this workaround approach viable, but not ideal for most situations. The spec would benefit from a more explicit definition of layer ordering within a buildpack.

### Alternative key names

Because naming is a known hard problem, we could use alternative key names:

- `order=1`
- `ordering=1`
- `layer_order=1`
- `layer_ordering=1`
- `load_order=1`
- `load-order=1`
- `priority=1`
- `load_priority=1`
- `precedence=1`
- Something else

My suggestion to buildpack authors would be that they provide the same order in which layers are written; this will mean that code evaluated within a buildpack will behave the same way as code in the `build` phase of another buildpack or at the `launch` time. Rather than introducing some abstract concept such as priority or precedence, I tried to be as literal with the name `order` and what it affects (the order in which layers are loaded). Therefore the suggestion is `load_order`.

### Alternative value formats

The value of `load_order` could be some other type: string, table, array, boolean, date, etc. It could also have different semantics. For example, it could be `load_after = ["<other-layer-name>"]` where like `load_after = ["gems", "ruby"]` where the order would need a resolution phase. While this is familiar to users of tools such as `rake` and `make` or other tools that support dependent tasks, it makes the ordering less clear and introduces failure points such as misspellings.

Choosing the value to be a positive numeric value and tying it to a name such as `load_order` it makes intuitive sense that `2` will be loaded after `1`. It also makes intuitive sense for buildpack authors that their load order should match their layer write order.

Buildpack authors can easily increment a value. Bash buildpack authors can do so like this:

```term
# Set an initial order value
export LAYER_LOAD_ORDER=0

# Increment it
LAYER_LOAD_ORDER=$((LAYER_LOAD_ORDER+1))
```

An alternative type could be "string," such as `layer_order=a`. This type is more permissive but less comprehensible to developers. Also, different languages may order strings in different ways. A stronger numeric (integer) type is preferable over a "strongly" typed value.

A value such as a date or time might not have sufficient fidelity and takes more computational power to parse and produce than an integer. It's also a benefit that the same ordering of layers (with an integer) will result in the exact same TOML files on disk.

### Alternative restrictions or behaviors to the key or value

- The `load_order` key could be required. This change would prevent ambiguous buildpacks from lingering. However, it would be a breaking change to the current ecosystem. Some buildpacks might have only a single layer and this is unneeded. For other buildpacks operating correctly today, there's no strong need to add this ordering information.
- A future RFC could add a configuration to a buildpack's `buildpack.toml` that would make omitting this value result in an error or warning.
We could make duplicate values an error. For example, two layers with `load_order=1` could generate an exception and stop the build process instead of falling back to alphanumeric layer name ordering.
- I chose to make the feature more permissive to make it easier to adopt. In the event of a duplicate value, the lifecycle could issue a warning, or we could make the warning/error behavior configurable via the `buildpack.toml`. Future RFCs could change the default behavior of duplicate keys.

### Alternative feature: Explicit identity

The current implementation uses the layer name as the identity of a layer and its location on disk. We could introduce an explicit `identity` key into the TOML, allowing us to decouple disk structure from how layers are referenced and identified. The identifier would be alphanumeric and used for layer ordering.

This scheme would be difficult to implement via a wrapper library (i.e., libcnb.rs could fairly safely auto-add a `load_order=1` based on the order of execution, but it would be surprising if it prepended `0001_<name>` to an identity). This feature would also introduce some ambiguity around the purpose and difference between a layer "name" and a layer "identity." It might be more generally useful for some future unspecified purpose. This proposal suggests that we investigate and introduce a specific solution if that need arises.

### Store the data somewhere that's other than the existing TOML file

We could introduce a `<buildpack-name>/order.toml` or some other file so that its name could be appended to the bottom of the order when a new layer is created.

Doing this in bash is difficult if the value is a TOML array. If the value is a TOML table, then it would have a meaningless value, which would be confusing. It is easier to forget needing to write two values to two files than to remember to write two values to one file (the existing `<layer>.toml` file).

### Non-toml alternative: Finalize layer binary

The current interface of CNBs relies on writing configuration and modifying files on disk. However, it doesn't have to be limited to this configuration mode. Introducing an executable such as `cnb_lifecycle` could inform how a layer is finalized. For example, a buildpack author could call `cnb_lifecycle layer:finalize <layer-name>` or use it to initialize a layer `$ cnb_lifecycle layer:init <layer-name>` and then the lifecycle can choose how to store ordering invocation information it would use it when launching a layer.

This approach would greatly depart from how buildpacks interact with the lifecycle—stating it here for completeness.


# Prior Art
[prior-art]: #prior-art

- This [lifecycle issue elaborates on the problems that come up when an environment (build/launch) disagrees on the ordering of layers](https://github.com/buildpacks/lifecycle/issues/1393)
- Prior [similar issue](https://github.com/buildpacks/lifecycle/issues/170)
- Spike implementing a [prefix ordering workaround for the `heroku/ruby` buildpack](https://github.com/heroku/buildpacks-ruby/pull/384).

# Unresolved Questions
[unresolved-questions]: #unresolved-questions

- What parts of the design do you expect to be resolved before this gets merged?
- All
- What parts of the design do you expect to be resolved through implementation of the feature?
- All
- What related issues do you consider out of scope for this RFC that could be addressed in the future independently of the solution that comes out of this RFC?
- Resolving https://github.com/buildpacks/lifecycle/issues/884 would make value identity comparison easier for lifecycle implementers.

# Spec. Changes (OPTIONAL)

[spec-changes]: #spec-changes

> Does this RFC entail any proposed changes to the core specifications or extensions?

Yes, these are discussed at a high level above.

# History
[history]: #history

<!--
## Amended
### Meta
[meta-1]: #meta-1
- Name: (fill in the amendment name: Variable Rename)
- Start Date: (fill in today's date: YYYY-MM-DD)
- Author(s): (Github usernames)
- Amendment Pull Request: (leave blank)

### Summary

A brief description of the changes.

### Motivation

Why was this amendment necessary?
--->