When "docker version --format json" did not return clean JSON (for
example, text from a shell startup file), validation stored 0.0.0 and
said that Docker was older than version 24. Validation now stores no
version and tells the user to run the command on the server. When the
version is too old, the message shows the detected version.
Closes#11089
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The legacy configuration hash encoded serialized environment variable rows,
so hashes from older versions never matched after an upgrade, and the
fallback compared the configuration with an empty one. Apps without a
deployment snapshot now report no pending changes, and their next
deployment rebuilds the image.
Closes#11101
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Import:
- Check every uuid in a bundle against a strict id format before any
write, and quote the SSH mux socket and key paths in local shell
commands. A bundle uuid could run commands in the Coolify container.
- Validate bundle fqdns, git source URLs, paths, networks, cron
expressions, and database ports; cap the number of items.
- Do not confirm that another team has a uuid.
- Refuse a project icon path with a uuid that is not a plain id.
Claim, finish, and migrate:
- Pass checklist step keys to wire:click with Js::from (stored XSS).
- Link only to https target server URLs.
- Validate target_instance_url in /transfer/complete; never prefill an
unverified URL for a transfer back; scope the transfer-back token to
write+deploy for 1 hour.
- Release on an owner marker only when it is newer than the own claim.
- Do not touch apps that only use the server as an additional
destination when an archive is released or deleted.
- Give Sentinel a new token on claim.
- Add a checklist step to remove the previous instance's SSH key.
- Block a transfer while backups or scheduled tasks run.
- Authorize refreshMigration and PrivateKey checkConnection, clear
tokens from Livewire state, hide migration log/error from members.
Archives:
- Refuse ip, user, port, private key, and Cloudflare tunnel changes.
- Throttle Enable management and require a free server slot for it and
for a transfer back.
- Skip cloud provider calls.
- Re-read force_disabled before each SSH command, so running jobs stop.
- Probe the import check target at the resolved address only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Rate limit the import check probe and refuse private, loopback, and
link-local targets on Cloud.
- Rate limit the SSH validation that an import starts, like other
server validations.
- Count only managed servers for the server limit, the limit check job,
and the subscription page. Archives of transferred servers are left out.
- Do not show the name of another team's archived server in the import check.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Volume backup notifications built links with route(), which uses APP_URL
in queue jobs and gave http://localhost links. Build them from base_url().
Closes#12285
- Skip TrimStrings and ConvertEmptyStringsToNull on the import route; an empty shared variable failed the whole import
- Keep compose parser versions, static ports, legacy variable keys, and only the bundle's variables and destinations
- Give the transfer back token the deploy permission that the import needs
- Move the Cloudflare tunnel DNS record with the server and park it on the source
- Refuse cloud server deletes, power on, tunnel and DNS changes, and scp for archives; skip their backup alerts and provider polling
- Add a column-level round-trip test, archive guard tests, and a live full-coverage transfer test
- Advance the GitLab App sequence in the dev seeder
- Build a valid compose device request (device_ids list, options mapping, blank driver)
- Map --gpus N to count and drop --gpus when GPU settings are enabled
- Validate only changed GPU values so stored legacy values keep working
- Detect NVIDIA GPUs and the container toolkit with the server details
- Show detected GPUs with a refresh button in the application GPU section
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- ServerSetting::patchStatus() summarizes the saved patch check
(updates, security updates, reboot required, error). Results saved
before security and reboot detection count as 0 and false.
- Grid cards show "N security updates", "N updates", "Patch check
failed", and "Reboot required" badges; the table gets a Patches
column from lg. The badge text is searchable.
- The toolbar shows a one-line team summary, for example "Patches: 2
servers with updates · 1 with security updates · 1 needs a reboot".
- Badges and the summary are shown only for servers where the user can
view the security pages (viewSecurity on the server), so members do
not see them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- New server settings: is_auto_security_updates_enabled and
auto_security_updates_time (maintenance window, HH:MM in the time zone
of the server). Both move with a server transfer.
- Debian/Ubuntu: install unattended-upgrades and write
52coolify-unattended-upgrades. `#clear` drops the distro origin lists,
so only the security origins stay (Debian <codename>-security, Ubuntu
<codename>-security, and the Ubuntu Pro ESM security origins). A
drop-in for apt-daily-upgrade.timer sets the time.
https://manpages.debian.org/bookworm/apt/apt.conf.5.en.htmlhttps://github.com/mvo5/unattended-upgrades/blob/master/README.md
- RHEL family: install dnf-automatic (dnf5-plugin-automatic on dnf5),
set upgrade_type = security and apply_updates = yes in
/etc/dnf/automatic.conf, and add a drop-in for dnf-automatic.timer
(dnf4) or dnf5-automatic.timer (dnf5).
https://dnf.readthedocs.io/en/latest/automatic.htmlhttps://dnf5.readthedocs.io/en/latest/dnf5_plugins/automatic.8.html
- Off removes the Coolify files, so the operating system default comes
back. Other package managers show "not supported".
- The schedule and the automatic updates share one form and one
unsaved bar. The remote commands run only when the automatic update
settings change; a failure keeps the old setting.
- Commands are written for non-root SSH users (tee and single sh -c
scripts) and were run in Debian 12, Ubuntu 24.04, Rocky 9, and Fedora
containers as root and as a sudo user.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- server_settings gets patch_check_result (JSON), patch_check_error,
patch_checked_at, and patch_check_frequency.
- The patches page shows the saved result and "Last checked" on load.
"Check for updates" queues ServerPatchCheckJob (manual) instead of
running SSH in the Livewire request. The job saves the result and
broadcasts ServerPatchCheckFinished on the server team channel; the
page reloads the saved result (with a 10 s poll as a fallback).
- A finished package update (callEventOnFinish) queues a new check on
the server side, so the page no longer depends on the browser.
ServerPackageUpdated is removed.
- The check frequency is a server setting (cron or preset, at most once
per hour). Empty keeps the staggered weekly default.
- The failure notification is sent only when the state changes from ok
to error, not on every check.
- Page access needs viewSecurity and every action needs update on the
loaded server; a member gets 403. Updates accept only a package from
the saved result.
- Server transfer: the frequency moves, the last result stays on the
source (runtime).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Resolve conflicts between main's security fixes and v4.5 features:
- Keep v4.5 features (log redaction, multi-S3 backups, transfer flow,
scheduled task modal, sidebar) and port main's fixes into them.
- Reuse RequiresDeployAbility for the Hostinger create endpoint.
- Align MCP tool abilities and descriptions with main's new deploy
requirements, and use deploy tokens in the affected MCP tests.
- Drop the duplicate DockerCleanupJobTest helper and tests, and the
exportBundle test for a method v4.5 removed.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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