Skip to content

[Bug]: is_container_label_readonly_enabled is rejected by the applications APIΒ #11092

Description

@tomas-kulhanek

Description and Error Message

is_container_label_readonly_enabled can be changed in the UI (app/Livewire/Project/Application/General.php:398) but is rejected by the API. Sending it to PATCH /api/v1/applications/{uuid} returns:

{
  "message": "Validation failed.",
  "errors": {
    "is_container_label_readonly_enabled": [
      "This field is not allowed."
    ]
  }
}

Because validation rejects the request as a whole, the field cannot be sent opportunistically either β€” one unlisted key fails the entire PATCH, including all the other fields in it. In our case that took every tenant of a fleet into an error state at once, since the field was folded into the same PATCH that carries domains and the basic-auth settings.

Looking at app/Http/Controllers/Api/ApplicationsController.php on main (2026-07-30), the field is on none of the three lists:

  • create $allowedFields (~line 1123) β€” absent
  • update $allowedFields (~line 2695) β€” absent
  • APPLICATION_SETTING_FIELDS (line 38), which is spread into both β€” absent; it currently holds is_git_submodules_enabled, is_git_lfs_enabled, is_git_shallow_clone_enabled, disable_build_cache, inject_build_args_to_dockerfile, include_source_commit_in_build, is_env_sorting_enabled, is_pr_deployments_public_enabled, stop_grace_period, docker_images_to_keep, is_gzip_enabled, is_stripprefix_enabled, is_raw_compose_deployment_enabled

Meanwhile the field is read all over the codebase as the gate for label regeneration (ApplicationsController:151, Advanced.php:165, General.php:312), and the UI branches on it in general.blade.php:115-141.

This reads as an oversight rather than a policy:

Why we need it. We manage a fleet of applications through the API. Each gets a platform hostname in a zone we control plus an optional customer domain in a zone we do not. Traefik on our servers has two ACME resolvers: letsencrypt (DNS-01 via Cloudflare, our zones only) and lehttp (HTTP-01, everything else). Coolify hardcodes tls.certresolver=letsencrypt into every router it generates, so a customer domain can never pass the challenge and never gets a certificate. We rewrite that single label in custom_labels β€” but every PATCH carrying domains regenerates the labels and drops the override, so it has to be re-applied and re-verified after each one. Taking ownership of the labels, which is exactly what the UI toggle does, would make this deterministic. Doing it by hand in the UI for every application does not scale and is not reproducible from provisioning code.

Expected Behavior

  1. PATCH /api/v1/applications/{uuid} with {"is_container_label_readonly_enabled": false} returns 200 and persists the setting, the same way the UI does.
  2. The field is likewise accepted by the create endpoints (POST /api/v1/applications/dockerimage and siblings), as is_container_label_escape_enabled already is.
  3. With the setting off, Coolify stops regenerating custom_labels, so a label set written over the API survives subsequent updates.
  4. Sending the field together with unrelated fields does not fail the whole request.

Steps to Reproduce

  1. Create an application through the API, e.g. POST /api/v1/applications/dockerimage with a domains value.
  2. Send PATCH /api/v1/applications/{uuid} with body {"is_container_label_readonly_enabled": false}.
  3. Observe HTTP 422 {"message":"Validation failed.","errors":{"is_container_label_readonly_enabled":["This field is not allowed."]}}.
  4. Open the same application in the UI, where the setting can be changed without any problem β€” confirming the restriction is API-only.

Example Repository URL

No response

Coolify Version

v4.1.2

Are you using Coolify Cloud?

No (self-hosted)

Operating System and Version (self-hosted)

Ubuntu 24.04.4 LTS

Screenshots / Visuals

No response

Additional Information

Suggested fix. Add is_container_label_readonly_enabled to APPLICATION_SETTING_FIELDS in ApplicationsController β€” it is spread into both the create and the update allow-lists. That is the same one-line change #10280 got for include_source_commit_in_build and #7886 / #8955 for is_container_label_escape_enabled.

One design question worth deciding first. With readonly disabled, the UI marks domains and the redirect direction as read-only (general.blade.php:115-141), because Coolify no longer manages those labels. Over the API that would mean PATCH {"domains": "..."} returns 200 while having no effect on routing, which is a confusing contract. In order of preference:

  1. reject the combination β€” 422 when domains is sent while is_container_label_readonly_enabled is, or is being set to, false;
  2. or document explicitly that domains is ignored in that mode.

I'm happy to open a PR for either, if you let me know which you'd prefer.

Related: #7886, #8954 / #8955, #10280 (same class of request), #9502 (fqdn in GET vs domains in PATCH).

Metadata

Metadata

Assignees

No one assigned

    Labels

    πŸ” TriageIssues that need assessment and prioritization.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions