#11092·coolify

[Bug]: is_container_label_readonly_enabled is rejected by the applications API

Author: tomas-kulhanekCreated Jul 30, 2026Updated Sep 16, 2026
Labels🐛 Possible Bug🌐️ API

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:

  • The genuinely powerful field, custom_labels, is already on the allow-list. An API client can already write arbitrary Traefik routers, middlewares and basic-auth hashes. The readonly flag grants no additional capability — it only decides whether Coolify regenerates over them.
  • The setting is freely toggleable in the UI, so changing it is evidently not considered dangerous.
  • APPLICATION_SETTING_FIELDS visibly grows on demand: is_container_label_escape_enabled was added in #7886 / #8955 and include_source_commit_in_build after #10280 — each time because someone hit exactly this 422.

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).