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.
An upgrade pulls new images and recreates the Coolify containers. A full
disk during an upgrade can leave Coolify broken.
- Measure free space on the Docker root dir and /data/coolify of localhost;
require 5 GB. An unknown value does not block.
- Manual upgrade: show the free and required space, with Run Docker cleanup,
Check again, and a confirmed Upgrade anyway override.
- Automatic upgrade: skip it and notify the team (disk usage channels, at
most once per 24 hours per server).
- upgrade.sh: stop before any change when the disk is low. A new 5th
argument skips the check (used by the override).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Block new upgrades while a recent upgrade is running, allowing retries after 15 minutes without a status update.
Add request timeouts and polling guards to keep upgrade progress responsive during restarts.
The Domains page polls every 2 seconds while a DNS check runs. Each poll
parsed the compose file, and the parser queued one ServerFilesFromServerJob
per volume. The jobs starved the DNS check jobs and deployments.
- Skip the compose parse in pollDnsChecks and keep the known services.
- Make ServerFilesFromServerJob unique per resource until it runs.
Fixes#12098
The container picker read its options from an Alpine x-data snapshot taken
on the first render, before wire:init loaded the containers. Livewire keeps
the Alpine state when it morphs, so resources with multiple containers
showed an empty picker. Publish the options as a Livewire property and
entangle them in Alpine.
Closes#12110
Add an authorized update button with activity logs to pull the latest image and recreate the tunnel container in the background.
Persist new Compose files under /data/coolify/cloudflared and support existing /tmp configurations. Cover updates, missing configuration, and member restrictions with tests.
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.
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
Every released Sentinel with traffic analytics (1.0.0 and later) has
the dashboard and series routes and the requests, bytes_in and app
fields, so the fallbacks for builds without them are not needed.
- Keep "no data" handling for Sentinel without traffic routes (0.0.x)
- Keep the resource-scope fallback, which Sentinel 1.0.1 needs
A staged upload always won over a server path, so an old upload that
was never imported could be restored instead of the chosen file.
- Record the selected source (checked server file or finished upload)
and import only from it
- Delete a stale staged upload when another source is used
Integration token, cloud provider token and cloud-init script edits
were audit-logged under the session team. After a team switch in
another tab, the owning team missed the entry and the other team saw
the resource name.
- Use the team of the edited resource for every audit row
- Remove credentials, query and fragment from secret manager base URLs
in the audit log
- /security/cloud-tokens returns 403 for members instead of crashing
on a null token list
- /settings/oauth redirects non-admins to the dashboard; it used the
missing route "home" and a wrong redirect return type
Login no longer accepts invitations, so the dashboard lists the
unexpired invitations for the user's email with a link to the
invitation page, where the user accepts them.
The domain rows of applications and previews, and the DNS validation
flags of application and service domains, were not locked. A user
could change them from the browser to skip the DNS check or store
fake DNS results.
An admin of the root team could delete the root user (id 0) and the
owners of the root team from the user list.
- Never delete the root user here
- Refuse a user whose root-team role is higher than the caller's
- Refuse the caller's own account; the profile has its own flow
- A queued deployment on a deleted server no longer stops the queue;
one deployment that fails to start does not stop the others
- Cancelling a deployment starts the queued deployments of the same
application on all servers, as a finished deployment does
When the user had no session team (for example after removal from the
team), the audit log page threw an error and returned 500.
- Return 403 when there is no session team or the user is not an admin
- SecretManagerLinks: validate settings when the integration token
changes, and save only validated keys
- Service Domains: lock domainRows
- Server DockerImages: lock images
These paths used the session team after the resource was loaded. After a
team switch in another tab they used the wrong team or denied access.
- ImportForm: use the database team for the server, S3 storages, and
the import
- SQLite ConnectApplication: list applications of the database team
- IntegrationTokenPolicy: check admin of the token team for view,
update, and delete
Subscription actions ran on the session team without a role check. A user
who switched the session to a team where they are only a member could
cancel, refund, or change that team's subscription.
- Lock the team id at mount and require admin or owner of that team
- Check the locked team and the role again in every billing action
- Reject actions after the session switched to another team
Check start prerequisites in database and deploy APIs and MCP controls.
Return HTTP 422 for blocked API starts and restarts. Show restart notices
and open UI logs only after an operation is queued.
Add API regression tests for blocked ClickHouse starts and restarts.
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.
- Hide proxy configuration contents from users without update permission.
- Select and validate backup S3 storage against the resource's team.
- Keep scheduled branch syncs green while a conflict PR remains open.
- Document external mount permissions and Sentinel traffic limits.
- Add regression coverage for authorization and backup storage selection.
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.
/settings/backup returned 500 when the coolify-db resource existed without
a scheduled backup. Configure backup now reuses the existing coolify-db and
creates only the missing schedule for the root team, without forcing id 0.
Closes#12074
Add audit events for account actions, server and application settings,
services, destinations, storage, backup schedules, and scheduled tasks.
Track changed fields and skip unchanged saves without logging file content.
Add regression coverage for audit events and changed-field detection.
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.
- ServerCheckJob uses the stored reachability state instead of a second SSH
probe. One failed probe no longer marks the server unreachable without
UNREACHABLE_THRESHOLD or marks all its resources exited.
- Do not dispatch ServerCheckJob for build servers and Swarm workers.
- Check cloud provider state every 5 minutes instead of every minute.
- ServerPatchCheckJob uses isFunctional() and drops the unused retries.
- Delete the unused ServerCleanupMux job.
- Disable SSH multiplexing per command. The global config change stayed in
the queue worker, so later jobs (also deployments) ran without mux.
Remove the periodic ConnectProxyToNetworksJob dispatch from
PushServerUpdateJob, the synchronous connect in ServerCheckJob when the
proxy is found running, and the connect-on-add in the server
Destinations component. Drop the now-unused
proxy.connect_networks_interval_seconds config option.
Replace the rate-limit/interval tests with a test asserting that no
jobs are pushed when the proxy is running.
Recovery attempts now back off exponentially, stop after 8 attempts, and
mark the execution as needing attention with a failure category. The
scheduler dispatches at most 100 recovery jobs per run, and a pending S3
cleanup no longer blocks new backups. S3 delete errors now raise with
their cause.
- References to locked (shown once) shared variables keep their reference
text in the UI, copy action, and API instead of the locked value.
- Remove the saved proxy configuration and validation logs from nested
servers (for example destination.server) in API responses unless the
token can read sensitive data.
- The services list API now applies removeSensitiveData to every service.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A renamed template keeps its old key as an alias, so API callers and
services stored with the old key still find the template.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>