Skip to content

Implement Custom Stacks RFC: allow docker:// URIs as stacks - #5492

Open
johha wants to merge 3 commits into
mainfrom
custom-stacks
Open

johha wants to merge 3 commits into
mainfrom
custom-stacks

Conversation

@johha

@johha johha commented Sep 28, 2026 •

Copy link
Copy Markdown
Member

Thanks for contributing to cloud_controller_ng. To speed up the process of reviewing your pull request please provide us with:

  • A short explanation of the proposed change:

    Implements RFC-0046 behind the disabled-by-default diego_custom_stacks feature flag. Buildpack and CNB apps can use a docker:// image URI as their stack without requiring a Stack database record.

    Custom stacks are used for staging, runtime LRPs, and tasks. They support custom buildpacks or admin buildpacks without a stack association. Registry credentials are stored encrypted and only complete username/password pairs are passed to Diego. Embedded credentials are rejected.

    Public registry example:

    applications:
      - name: custom-stack-app
        stack: docker://cloudfoundry/cflinuxfs3:0.379.0

    The stack can also be provided directly to cf push:

    cf push custom-stack-app -s docker://cloudfoundry/cflinuxfs3:0.379.0 -b https://example.com/buildpack.zip

    Private registry example:

    applications:
      - name: private-stack-app
        stack: docker://ghcr.io/example/private-stack:latest
        credentials:
          ghcr.io:
            username: example
            password: ((registry-password))

    During staging, the final image repository component is exposed as CF_STACK or CNB_STACK_ID. Custom stack image layers count toward the app instance disk quota.

  • An explanation of the use cases your change solves

    Operators can allow apps to use patched, specialized, or independently maintained root filesystems without registering platform stacks. This also supports private registries and apps that must continue using an image after its corresponding platform stack has been removed.

  • Manual test coverage

    • Confirmed the existing system-stack workflow before and after the custom-stack tests.
    • Staged and ran classic buildpack apps with public and private custom stacks. Missing and incorrect credentials failed without exposing secrets; correct credentials were retained for later pushes, restages, restarts, scaling, and tasks.
    • Staged and ran CNB apps with public, private, and arbitrarily renamed custom stacks, including a private CNB buildpack. Restages, restarts, scaling, and tasks reused the stored credentials.
    • Used explicit custom buildpacks and auto-detected stackless admin buildpacks. Verified CF_STACK and CNB_STACK_ID derivation, a modified /etc/os-release, and the expected staging failure when image and buildpack ABIs were incompatible.
    • Disabled the feature flag and confirmed that pushes and restages were rejected while an already-running app remained healthy. Also verified operation after removal of the corresponding platform stack.
    • Verified early validation of malformed image URIs, embedded credentials, and stack-pinned admin buildpacks.
    • Confirmed that custom stack image layers count toward disk quota: undersized quotas failed, while app instances and tasks succeeded with sufficient quota.
  • I have reviewed the contributing guide

  • I have viewed, signed, and submitted the Contributor License Agreement

  • I have made this pull request to the main branch

  • I have run all the unit tests using bundle exec rake

  • I have run CF Acceptance Tests

@johha
johha force-pushed the custom-stacks branch 10 times, most recently from 423f2ae to 15f6b1c Compare October 23, 2026 12:17
@johha
johha marked this pull request as ready for review October 28, 2026 14:57
Add custom container-image stacks for buildpack and CNB lifecycle apps
behind the diego_custom_stacks feature flag.

- Accept and validate docker:// stack image URIs without creating Stack
  records or persisting the deprecated stack_id field.
- Use custom root filesystems for staging, runtime LRPs, and tasks, with
  lifecycle-bundle fallback for droplets whose original stack was removed.
- Support custom buildpacks and stackless admin buildpacks, including
  buildpack auto-detection.
- Store registry credentials encrypted, redact them from app events, reject
  credentials embedded in stack URIs, and pass only complete matching
  username/password pairs to Diego.
- Reject unsupported token credentials for custom stack registries while
  retaining token support for CNB buildpack images.
- Derive CF_STACK and CNB_STACK_ID from the final image repository
  component, excluding tags and digests.
- Skip stack-record validation and buildpack-cache handling where they do
  not apply to remote custom stack images.
- Document custom stack syntax, credentials, buildpack restrictions, stack
  identifiers, and image-layer disk quota behavior.
- Add coverage for validation, lifecycle staging, desired LRPs, tasks,
  credentials, feature-flag enforcement, manifests, and event redaction.

Co-authored-by: FloThinksPi <5863788+FloThinksPi@users.noreply.github.com>
@jochenehret

Copy link
Copy Markdown
Contributor

Tested on a bbl environment with a default CF deployment, works!

But I also tested some error scenarios and found a few confusing error messages and a few minor issues:

  1. When pushing an app with a custom stack and without a buildpack, you get this error message:
For application 'custom-stack-app': Buildpack must specify a custom buildpack URL or an enabled unpinned admin buildpack when using a custom stack
  FAILED

Maybe better: "You must either specify a custom buildpack URL or your platform operator must provide an enabled buildpack not pinned to a stack."

  1. Using a non-existent image like "docker://ghcr.io/example/cflinuxfs4:1.332.0" results in error message:
   Cell c57eb448-3919-452b-8783-13d4043f1850 failed to create container for instance 414f282c-bd44-4ca0-ae8a-a330b6531d45: running image plugin create: fetching image reference: creating image: creating image source: Requesting bearer token: received unexpected HTTP status: 403 Forbidden
   : exit status 1

Shouldn't the error say something like "404 not found"?

  1. Using an invalid stack URL produces a confusing error message:
Updating with these attributes...
  ---
  applications:
  - name: custom-stack-app
    disk-quota: 2G
+   default-route: true
-   stack: docker://cloudfoundry/cflinuxfs5:0.63.0
+   stack: ocker://cloudfoundry/cflinuxfs5:0.63.0
    buildpacks:
      https://github.com/cloudfoundry/staticfile-buildpack
For application 'custom-stack-app': Stack must be an existing stack
FAILED
  1. Specifying a non-existent tag produces a confusing error message:
-   stack: docker://cloudfoundry/cflinuxfs5:0.63.0
+   stack: docker://cloudfoundry/cflinuxfs5:0.63.777

   Cell c57eb448-3919-452b-8783-13d4043f1850 failed to create container for instance 47c2baeb-1174-48c3-994c-c8802f938539: running image plugin create: fetching image reference: creating image: creating image source: reading manifest 0.63.777 in docker.io/cloudfoundry/cflinuxfs5: manifest unknown
   : exit status 1
  1. The environment variable CF_STACK is not set in the running container (only in staging). Is that intended?

@johha

johha commented Oct 29, 2026

Copy link
Copy Markdown
Member Author

Thanks for the feedback @jochenehret . I addressed the points as follows:

  1. Updated the buildpack validation message to: “You must either specify a custom buildpack URL or your platform operator must provide an enabled buildpack that is not pinned to a stack.”
  2. The 403 is returned by GHCR while resolving the image. CAPI validates the URI syntax, while Diego reports the registry response, which is not necessarily a 404. Therefore nothing we can change on CC side.
  3. Improved the validation message when custom stacks are enabled: “Stack must be an existing stack or a valid container image URI starting with docker://.”
  4. Tags are resolved by the registry when Diego pulls the image. manifest unknown is the upstream registry response; CC cannot reliably validate tags beforehand, especially for private registries.
  5. This is intended. CF_STACK and CNB_STACK_ID have historically only been set during staging. The documentation and PR description now state this explicitly.

This branch has not been deployed

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants