Require deploy permission for API changes that trigger server side effects, including resource deletion, storage access, provider operations, and database imports. Also escape Docker cleanup filters and sanitize log drain server names.
oldRawParser made the bind directory of a long-syntax source like
../data at {workdir}./data, next to the path that Docker Compose uses.
Resolve the source with replaceLocalSource(), as the compose parsers do,
so Coolify makes the directory that the container mounts. A source that
resolves above / now stops the deployment before remote work.
Closes#4055
A custom config file written with tee gets the server umask. With umask
027 or 077 the file is not readable by the container user, so MySQL and
MariaDB ignored the whole config without an error.
- Set mode 644 on the MySQL, MariaDB, PostgreSQL, and MongoDB custom
config files after each write.
- Close the multiplexed SSH connection when the server user, IP, or port
changes, so the next command does not run as the old user.
Closes#11600
validateOS matched only ID from /etc/os-release, so derivatives such as
Omarchy, CachyOS, Manjaro or Linux Mint failed validation before the
Docker checks ran. Check ID first, then each ID_LIKE entry, and keep the
exactly-one-match rule. Move the parsing into a testable function.
Closes#12033
Co-authored-by: Furox <177975472+Furox-Art@users.noreply.github.com>
The repository URL rule rejected ssh://user@host:port/path, so deploy-key
resources on servers with a custom SSH port needed a manual URL edit after
creation. Accept ssh:// URLs and keep them unchanged for deploy keys so the
port reaches the clone. Rename scpStyleGitUrlToHttps to sshGitUrlToHttps and
let it convert ssh:// URLs too, so public flows, repository links and GitHub
App repository slugs handle them.
Closes#12161
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Fetch every page of pull request files so watch paths match PRs with more than 30 files.
- Deploy the preview and log a warning when the changed-file list is unknown (API error or truncated compare list).
- Log when watch paths decline a preview deployment.
- Keep the GitHub App installation token in memory per instance, so a deployment requests one token instead of 3-5.
Closes#12169
Count invalid keys, tokens, and deleted webhooks in a new failure_count column
on each notification settings table, like servers.unreachable_count. After 3
failures in a row the channel is disabled and the team is told by instance
email and its other channels. Rate limits and server errors are retried and
not counted; test notifications never count.
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
- Stop the Redis username accessor from creating REDIS_USERNAME on read
- Keep source values when cloning applications (no duplicate NIXPACKS_NODE_VERSION)
- Match exact keys when adding default database environment variables
- Write content files before database containers start
- Resolve the database proxy upstream through Docker DNS on each connection
- Remove existing duplicate REDIS_USERNAME and NIXPACKS_NODE_VERSION rows
Store server IPs in one form (no brackets, short lower-case IPv6) and
write each address in the form its context needs:
- URLs and host:port (Sentinel, database URLs, SFTP backup download,
DNS servers, SMTP, webhook base URLs, links, health checks) get
brackets through formatHostForUrl().
- Traefik Host() rules use the address without brackets, so IPv6
domains no longer return 404; saved labels are fixed at deploy.
Caddy keeps the brackets.
- SSH keeps the plain address.
- Hetzner gives an IPv6 /64 network; IPv6-only servers now get the
server address (prefix::1) instead of the unusable prefix::64.
- sslip labels, registry image names with an IPv6 host, the API domain
list, allowlist matching and duplicate checks handle IPv6.
A migration normalizes stored IPv6 server addresses, repairs Hetzner
servers saved as ::64 that are not reachable, and adds brackets to
saved Sentinel URLs.
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
Allow full, anonymized, or disabled IP recording per server through the UI
and API, and pass the selected mode to Sentinel. Adapt traffic breakdowns
to the effective IP mode, including views that aggregate multiple servers.
Check enabled volume backup schedules hourly and notify teams when no
execution occurs within the configured number of days. Expose the alert
threshold in the UI and API, with alerts disabled by default.
Route volume backup results and warnings to the appropriate Telegram
backup threads.
Keep reserved jobs active and bound missing Horizon records by the server's
deployment timeout. Add regression coverage for active and finished jobs.
Use UTC PostgreSQL sessions and bump the release version to 4.4.1.
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
Connection URLs of KeyDB, Dragonfly and Redis queried their password
variables once per database, so the database list API ran extra queries
for every database.
- Read REDIS_PASSWORD and REDIS_USERNAME from the loaded relation
- Load the variables once per type in the database list and the
environment details API
Queued deployments of a deleted server could never start and stayed in
the queue. Deleting a server now marks them failed with the log entry
"The server was deleted."
v4.3.23 started Redis with the stored REDIS_PASSWORD value as it is and
unquoted, and the connection URLs used the same value. 4.4.0 resolved a
shared variable reference for the start command only, so after the first
restart the server password changed and no longer matched the URLs.
- Add legacy_password_quoting to standalone_redis; existing databases
keep the v4.3.23 server password
- New databases resolve REDIS_PASSWORD (local, shared or remote secret)
and quote it in the start command
- Connection URLs use the same password as the server
- Hide the flag in the API, as for KeyDB and Dragonfly
The stored password is the default. Before, a local REDIS_PASSWORD was
rewritten to the stored password, and a shared one replaced the stored
password, so the old password was lost.
- A local, shared, or remote secret REDIS_PASSWORD sets the server
password; the stored password and the variable are not changed
- Removing the variable, or an empty value, uses the stored password
- Connection URLs show the password in use; for a remote secret they
show the reference, not the secret
- Databases created before this release keep the v4.3.23 behavior
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
Service compose and .env content was sent inline in the SSH command.
Linux limits one argument to 128 KiB, so a service with a generated
compose over about 96 KB failed to save or start with "Argument list
too long".
- Add instant_remote_write_file(), which streams content to a remote
tee over SSH stdin (sudo tee for non-root SSH users)
- Add SshMultiplexingHelper::generateSshStdinCommand() with the same
SSH options as generateSshCommand()
- Write the service compose and .env with the new helper
Scope resource permissions and related records to the owning team across
team switches, cloning, deployments, and resource operations.
Merge ClickHouse volume mounts, preserve anonymous data volumes on stop,
and require adopting current data before starting or restarting.
Fix file storage deletion checks and add regression coverage.
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.
Allow validated host file and directory mounts outside resource directories
while protecting external paths from deletion.
Clean up cancelled deployment containers and clear tracked process IDs.
Preserve legacy KeyDB and Dragonfly password parsing, accept server backup
paths without extension restrictions, and allow Compose variable proxy ports.
Add Application::isGithubAppSource(), which checks that the source is a
GithubApp, and use it in place of is_github_based() for pull request
status updates in ApplicationDeploymentJob and for the "Load pull
requests" action in the previews view.
Previews::load_prs now throws an error for non-GitHub App sources
instead of calling the GitHub API with a GitLab or other source. Also
adds a test checking that GitLab sources don't show or load GitHub pull
requests.