Updating the image of a service named like "api.test" wrote a nested
"test" key into the "api" service, because data_get/data_set split the
name on dots. Use array key paths instead. Also refuse service names that
Docker cannot use as a container name prefix, with a clear error on save.
Closes#12456
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Add a hint to failed command messages when the exit code is 255,
which explains that the SSH connection to the server closed before
the command finished.
- Return no services from applicationParser and serviceParser when the
compose `services` key is empty or is not a map, so parsing does not
fail.
- Move Server::getIp() out of the sshEndpointInUse() docblock, where it
was misplaced and broke the class.
- Add tests for the SSH hint, for services keys that are not maps, and
for saved compose variable values that must survive a redeploy
(#6995).
A preview renames its services to <name>-pr-<id>, so services that call
each other by their Compose names failed to start. Add the original name as an alias on the preview network.
Closes#8030
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A preview deployment set COOLIFY_BRANCH in three places with three
values: the Compose shell variables used the application branch
(main), the Compose parser used pull/<id>/head, and other build packs
used the fetched refspec (pull/<id>/head:pr-<id>-coolify) or the
application branch. A variable that refers to ${COOLIFY_BRANCH} then
got a different value than the container. All paths now use
Application::coolifyBranch(), which gives pull/<id>/head for previews
and git_branch for production deployments.
Closes#9572
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Public pull request previews no longer get production values, and no
preview falls back to production variables. Existing previews were
deployed with these values; for example, their databases were
initialized with the production password, so they broke on redeploy.
Previews that exist at the upgrade get keeps_production_values and
keep the earlier behavior. Previews created later do not. Server
transfer moves the flag.
The compose parser queued ServerFilesFromServerJob for every volume on
each parse, and pages that only show data parse the Compose file. Each
sync also sent two SSH commands for each file.
- Queue the sync only for a compose deployment, and for each service
resource with file storages when the service starts or deploys.
- Read the compose service names without a parse on the Domains and
scheduled task pages.
- Check all file storages with one remote command, and stop when a
symbolic link, a special file, or an unknown state is at a file path.
- Keep the application path in a file storage when a pull request
preview is parsed. Preview deployments of the current parser write
their `-pr-<id>` copies through LocalFileVolume::forPullRequest().
- Remove the unreachable file sync from the service resource page.
Docker Compose resolves these values from the .env file, so the parser no
longer rejects or misresolves them. Also copy the expected parser fixtures
when parser-snapshot-diff compares with a git ref.
A service that declares SERVICE_FQDN_X_PORT while the named service declares
SERVICE_URL_X_PORT (same value) now counts as a copy, like an exact copy, so it
does not take the named service's domain. Also replace the Postgres-only
regex in the magic variable loop with the same check in PHP.
When a compose service copies another service's SERVICE_URL_/SERVICE_FQDN_
declaration (old Twenty template: worker repeats SERVICE_URL_TWENTY_3000),
the parser and updateCompose wrote the variable from the copier's generated
domain, so a custom domain on the named service was lost on every deploy.
Skip exact copies; declarations with another path or port are unchanged.
Closes#11924
Applications and service applications can now use the server resolver,
no resolver ("none"), or a custom resolver name. This allows servers
that mix Let's Encrypt domains with domains that use their own
certificates, such as Cloudflare Origin CA. The setting is available in
the UI and the API.
Closes#12138
Coolify always added tls.certresolver=letsencrypt to generated HTTPS
routers, so Traefik logged errors when the proxy used its own
certificates (for example Cloudflare Origin CA) without that resolver.
Add a server setting (default letsencrypt) in the proxy page and API.
An empty value omits the resolver from resource labels, the Coolify
dashboard routes, and the catch-all router.
Closes#12138
Accept the `?`, `:?`, `+` and `:+` interpolation operators in Compose
network names, such as `${CONTAINER_NAME?error}`. The validation added
in v4.4.0 only knew `-` and `:-`, so these valid files stopped deploying.
The text after `?` is an error message and is not used as the default of
the created environment variable. Unsafe operands are still rejected.
Fixes#12126
Sentry self-hosted (getsentry/self-hosted 26.8.0) could not be deployed
as a Docker Compose application. Three separate problems blocked it:
- YAML collection aliases: the first fix raised the limit to 256 only in
Application::oldRawParser(). The safety check added in v4.4.0
(validateDockerComposeForInjection) and all other Compose parse calls
still used the Symfony default of 128, so the Sentry file (133 aliases)
failed with "Maximum number of collection aliases (128) exceeded".
Add parseDockerComposeYaml() and use it for every Compose parse.
- Argument list too long: the deployment job sent the generated Compose
file as base64 inside the SSH command. Linux limits one argument to
128 KiB, and the Sentry file is about 370 KB. execute_remote_command()
now accepts an 'input' key that streams content over SSH stdin, and
the job writes the Compose file with it.
- A list-style build arg without a value (build.args: [SENTRY_IMAGE])
merged into a map environment kept its numeric key, so Docker Compose
rejected the file with "non-string key in ...environment: 0". It now
becomes NAME with no value, as in Compose.
Closes#11725
Since aabd12737 a Compose bind volume with a content block had to stay
inside the resource directory. Services and Compose applications saved
in v4.3.23 with other paths, and Git Compose files with them, no longer
started or deployed.
Only admins and owners can edit Compose files, and they can already
write any host file with a plain bind mount, so the rule added no
security.
- Write content files to their host path again, as in v4.3.23
- Keep shell escaping and the safe-character checks on volume sources,
so injection attempts are still rejected
Previews of Docker Compose applications used external volumes as
written, so a preview mounted the production volume. Two database
containers on the same data can corrupt it.
- A preview uses {uuid}_<name>-pr-N with a name-only declaration, the
same name as v4.3.23, so old preview volumes are used again
- Production still uses the external volume as written
- Preview cleanup removes the preview volume, never the external one
- Do not show the external volume warning for previews
Require deploy or root token access when creating managed mounts outside
resource directories or changing their file contents through the API.
Synchronize KeyDB and Dragonfly passwords with local and shared
REDIS_PASSWORD values while preserving secret manager resolution.
A preview volume no longer gets driver_opts that name a host device, so it
does not mount the same folder or disk as the production volume. tmpfs
options are kept.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Before Coolify kept `driver`, `driver_opts` and `labels` of renamed
Compose volumes, Docker created those volumes without them. Applying the
options now would make Docker Compose ask to recreate the volumes on
every deployment.
- Add `ignores_compose_driver_options` column to local_persistent_volumes
and mark all volumes that exist at upgrade time
- Parsers now use composeRenamedVolumeDeclarationFor() so flagged volumes
keep the old name-only top-level declaration
- Legacy application parsers (v1/v2) always emit name-only declarations
- Reset the flag when a volume is replicated, since a clone is a new
Docker volume
- Include the flag in server transfer export/import; bundles without it
default to true
- Storages UI shows which volumes ignore driver options and allows
deleting them so the options apply on recreation
- Add tests for parsers, cloning and server transfer
- database import: add keep_owners (restore PostgreSQL owners and
privileges) and sqlite_database (pick the target SQLite file) to the
API, OpenAPI spec, and import form
- oauth: require password confirmation and support team selection on
OAuth login
- remote process: send team broadcast events to the team of the server
that ran the process instead of the causer's user id
- servers API: handle the legacy build server setting
- compose: support parent bind paths and volume driver options
- dns: improve record hints and provider management
- notifications: fix throttling and missing backup notifications
- ui: rework the confirmation modal
- add feature and unit tests for the new behavior
Accept Compose variables ($NAME, ${NAME}, ${NAME-default},
${NAME:-default}) in the `name:` of an external volume, and in the old
`external: {name: x}` syntax. Docker Compose resolves them from the
deployment .env. Coolify never removes external volumes, so it does not
need the resolved name.
The volume key must still be a literal Docker volume name, because
Compose does not resolve variables in keys. Names that are unsafe or use
unsupported syntax are still rejected, for example command substitution,
spaces, escaped dollars, and ${VAR:?err}.
Add tests for application and service parsing, legacy volume rows, the
StackForm save, legacy Compose parsing, and the rejected cases.
Container ownership labels now use UUIDs: coolify.applicationUuid,
coolify.serviceUuid, coolify.service.subUuid and coolify.databaseUuid.
Numeric ids differ between Coolify instances, so after a server transfer
the target could not find existing containers: redeploys left the old
container running and service statuses were wrong.
Containers are matched by UUID label first, then by the compose project
(Swarm: stack namespace), which is the resource UUID since July 2024, and
last by the older numeric id label. Existing containers keep working
without a redeploy and get the UUID labels on the next deploy or restart.
Legacy Compose applications keep the old name of an external volume. The
deployment log now tells the user why and how to move to the external
volume. The storage page shows external volumes as a read-only list.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Switching an external volume from the old <uuid>_<name> volume to the
declared external volume on redeploy could make an app look as if it
lost its data (for example a database that initializes a new, empty
data directory).
- If a resource already has the storage entry with the old generated
name, the parsers keep using that volume and show a warning in the
deployment log and the service start log, with the steps to switch.
- Without that entry (new resources, or external volumes added later)
the external volume is used as declared.
- Deleting the old storage entry (storage page) switches to the
external volume on the next deployment; the storage entry shows a
note about it.
- Legacy Compose applications (parser versions 1 and 2) never stored
their volumes, so they keep the old volume names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Compose volumes declared with external: true (or external: {name: x})
were renamed to <uuid>_<name>, so containers got a new empty volume
instead of the existing one. All Compose parsers (applications,
previews, services, and the legacy parsers) now use them as written:
no uuid prefix, no preview suffix, the top-level declaration kept, and
no tracking row, so Coolify never removes a shared external volume.
External volume names must be literal; ${VAR} names are rejected.
Existing <uuid>_<name> volumes and their rows are kept on redeploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Development mode rewrote /data/coolify paths to the dev Docker volume
for every server. That is only right for the testing-host backend,
which uses the host Docker daemon. The KVM localhost and other dev VMs
have their own /data/coolify, so bind mounts, Compose content files,
the Traefik and Caddy traffic mounts, Sentinel, and database proxies
pointed to wrong paths there.
- Server::sharesDevHostDocker() and devHostDockerPath() decide per
server; only the testing-host server (ip coolify-testing-host) gets
the volume path. Production is unchanged.
- The testing-host volume names come from the Compose project
(DEV_COOLIFY_DATA_VOLUME, DEV_COOLIFY_BACKUPS_VOLUME), with the
legacy names as fallback.
- In development, saved proxy configurations with the old path are
fixed when they load and ask for a proxy restart.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A Compose bind volume with a Coolify `content:` block could make
Coolify write any host file (for example /root/.ssh/authorized_keys)
when the file was loaded. Content sources must now be inside the
resource directory (./ paths); other sources get a clear validation
error for services and applications (UI, API, load, deployment),
and every content write is confined again on the server. Bind
mounts without content are unchanged. All 94 template content
volumes use ./ paths and still work.
- Quote container names in scheduled tasks and in the stop actions for
applications, previews, and services.
- Fix a development-only crash when a preview with a bind mount was
parsed (the preview suffix returned a plain string).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Git-based Docker Compose applications skipped the Compose injection
validation that services use. It now runs when the file is loaded or
reloaded, when the raw Compose is saved (UI and API create), and at
deployment before any command uses the file; an unsafe file is not
saved, and a deployment stops with a clear log line.
- All 371 service templates and realistic Compose files still pass.
- Network names may now mix text with $VAR, ${VAR}, ${VAR:-default},
or ${VAR-default} (for example ${COMPOSE_PROJECT_NAME}_default), so
such existing applications keep deploying; command substitution and
unsafe defaults stay rejected.
- Quote the preserved-repository path in a stat command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A Docker Compose application could use ${VAR} as a network name, but
only services created the variable. With ${VAR:-default} the value was
hidden and could not be edited; with ${VAR} Compose got an empty name
and the deployment failed. Applications now get the variable (and its
preview copy) with the default from the compose file, like services.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Traffic analytics now covers every resource on both proxies:
- Caddy: Compose applications and services get access-log labels. The
log_append key is {uuid}-{service}, the same key Sentinel reads from
Traefik router names.
- A resource owns the Sentinel key {uuid} and every key that starts
with {uuid}-. Application pages and the traffic card now merge all
keys, so Compose applications and previews show data on Traefik too.
- Global analytics: the leaderboard groups the keys of one resource
into one row, lists services, and never names keys of other teams.
- New service analytics page (project.service.analytics) with the same
view and authorization as the application page.
- Switching the proxy type restarts Sentinel when analytics is on, so
it reads the new log path. Sentinel gets no traffic mount when the
proxy has no analytics support.
- Caddy servers show a note that resources log only after a redeploy.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Accept a top-level network `name:` that is a single Compose variable
(`${VAR}`, `${VAR:-default}`, `${VAR-default}`), e.g. for external
networks that differ per server. The default must still be a valid
network name; surrounding text, nested variables, and shell syntax
are still rejected.
When parsing services, such variables are created as service
environment variables (seeded with the default) so users can view and
change them, while the compose file keeps the variable for Compose to
resolve from .env at deployment. Existing values are preserved on
re-parse.