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
PATCH /api/v1/applications/{uuid} with {"is_container_label_readonly_enabled": false} returns 200 and persists the setting, the same way the UI does.
- The field is likewise accepted by the create endpoints (
POST /api/v1/applications/dockerimage and siblings), as is_container_label_escape_enabled already is.
- With the setting off, Coolify stops regenerating
custom_labels, so a label set written over the API survives subsequent updates.
- Sending the field together with unrelated fields does not fail the whole request.
Steps to Reproduce
- Create an application through the API, e.g.
POST /api/v1/applications/dockerimage with a domains value.
- Send
PATCH /api/v1/applications/{uuid} with body {"is_container_label_readonly_enabled": false}.
- Observe HTTP 422
{"message":"Validation failed.","errors":{"is_container_label_readonly_enabled":["This field is not allowed."]}}.
- 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:
- reject the combination β 422 when
domains is sent while is_container_label_readonly_enabled is, or is being set to, false;
- 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).
Description and Error Message
is_container_label_readonly_enabledcan be changed in the UI (app/Livewire/Project/Application/General.php:398) but is rejected by the API. Sending it toPATCH /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
domainsand the basic-auth settings.Looking at
app/Http/Controllers/Api/ApplicationsController.phponmain(2026-07-30), the field is on none of the three lists:$allowedFields(~line 1123) β absent$allowedFields(~line 2695) β absentAPPLICATION_SETTING_FIELDS(line 38), which is spread into both β absent; it currently holdsis_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_enabledMeanwhile 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 ingeneral.blade.php:115-141.This reads as an oversight rather than a policy:
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.APPLICATION_SETTING_FIELDSvisibly grows on demand:is_container_label_escape_enabledwas added in feat(api): allow to escape special characters in labelsΒ #7886 / fix(api): allow is_container_label_escape_enabled in service operationsΒ #8955 andinclude_source_commit_in_buildafter [Bug]: Expose include_source_commit_in_build via the REST APIΒ #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) andlehttp(HTTP-01, everything else). Coolify hardcodestls.certresolver=letsencryptinto every router it generates, so a customer domain can never pass the challenge and never gets a certificate. We rewrite that single label incustom_labelsβ but every PATCH carryingdomainsregenerates 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
PATCH /api/v1/applications/{uuid}with{"is_container_label_readonly_enabled": false}returns 200 and persists the setting, the same way the UI does.POST /api/v1/applications/dockerimageand siblings), asis_container_label_escape_enabledalready is.custom_labels, so a label set written over the API survives subsequent updates.Steps to Reproduce
POST /api/v1/applications/dockerimagewith adomainsvalue.PATCH /api/v1/applications/{uuid}with body{"is_container_label_readonly_enabled": false}.{"message":"Validation failed.","errors":{"is_container_label_readonly_enabled":["This field is not allowed."]}}.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_enabledtoAPPLICATION_SETTING_FIELDSinApplicationsControllerβ it is spread into both the create and the update allow-lists. That is the same one-line change #10280 got forinclude_source_commit_in_buildand #7886 / #8955 foris_container_label_escape_enabled.One design question worth deciding first. With readonly disabled, the UI marks
domainsand the redirect direction as read-only (general.blade.php:115-141), because Coolify no longer manages those labels. Over the API that would meanPATCH {"domains": "..."}returns 200 while having no effect on routing, which is a confusing contract. In order of preference:domainsis sent whileis_container_label_readonly_enabledis, or is being set to,false;domainsis 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 (
fqdnin GET vsdomainsin PATCH).