Replace the dev-only management ownership section with an Enable/Disable
management button in the Connection section header, and place the
Connection section above the Server overview.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The dashboard redesign added an IP address column to the servers list
and put each server IP into the Alpine data, so the IP showed in the
page source. Remove the column and the value, and render the component
in the test instead of matching template strings.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Replace the browser confirm on the Delete button with a two-step
confirmation modal. To delete, users now type the image reference.
The modal is shown only to users who can update the server. delete()
now accepts an optional password argument so it works with the modal's
submit action.
Adds an Images entry to the server sidebar that lists the Docker images
stored on that server: repository, tag, image ID, age, size and the
containers using each one. Images no container references are labelled
Unused and can be deleted individually; Docker's own reported total and
reclaimable size are shown above the table.
Container logs and scheduled-task commands and output are now shown only to
users who can edit the resource. The API returns them only to tokens with
the read:sensitive or root ability, the same as the MCP tools.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a Management ownership switch to the server page, shown only in
development, that lets a user with update permission release or claim
control of a server from this Coolify instance. Claiming is blocked for
localhost and when the team is over its server limit. Both actions are
recorded in the audit log.
When ServerTransferClaimer claims a server, it now clears force_disabled
if the team is within its server limit, and it always saves settings,
not only when the sentinel is rebound.
TrustHosts skipped its check while the cache key instance_settings_fqdn_host
was empty, and only the skipped check filled that key. So since e1fe58639
(October 2025) no release restricted the Host header. Turning it on now would
lock out users who reach Coolify by server IP or another name when the domain
or proxy is broken.
Links in emails and notifications do not use the Host header: base_url()
uses the configured FQDN or the server IP (see ResetPasswordUrlTest). So
remove the middleware, its cache key and its tests. TrustProxies is unchanged.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove tests that copy app logic into the test, grep PHP source as
text, only check literals, constants, class_exists or PHP built-ins,
duplicate other tests, or are always skipped. Keep source checks that
guard security and authorization rules.
Rename a duplicate Pest helper so the full suite loads, drop the
missing v5/Browser path, and remove the CLAUDE.md symlink in favor of
AGENTS.md. Correct AGENTS.md: Dusk is not installed and the upgrade
script is scripts/upgrade.sh.
cleanup:database used orderBy()->skip(10)->delete(). Laravel drops the offset
in a DELETE, so it deleted all old rows anyway, and the count query with the
offset printed 0 on PostgreSQL. Count without the offset, delete in batches of
1000 rows so a large table is not locked by one long DELETE, and write the
output through the command so it is captured.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reject delete_from_provider values that are not booleans, and reject the
request when the team has no token for a linked cloud provider. Before,
both cases removed the server from Coolify but kept the cloud VM running.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With APP_ENV=local the deployment log showed every build-time value (raw,
stripped and escaped) and the old and new value of overrides. Print only the
key, the type and the value length.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
remote_process() stored the full shell command as plain text in
activity_log and kept it for 60 days. Database starts store the compose file
with the database passwords, imports store S3 keys, and the Cloudflare
tunnel setup stores its token.
- Store the command encrypted. The queue worker decrypts it right before the
SSH call and still reads plain commands of tasks queued before the upgrade.
- Remove the command when the task is finished: after a successful run, in
failed() when no attempt is left, and for a stopped import. A failed attempt
that the queue retries keeps it.
- Do not write a command preview into the error log.
- Migration: remove stored commands from activities that no longer run.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The All button read $wire.numberOfLines, which does not update while the user
types or pastes a new number, so All stayed highlighted. Keep the state in
Alpine instead: All loads set it, and any input in the number field clears it
unless the value is -1.
Show a spinner in the All button while all logs load, and in place of the
Lines label while a new line count loads. Both keep their width.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The import looked up the key in all teams and stopped with "already exists
on another team", which also told the user that another team has the key.
Keys are unique per team, so reuse a key of the target team or create one.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Login, registration and OAuth set a currentTeam attribute on the user model
that is not a database column, so a save of the user in the same request
would fail. Keep the team in the session and read it with currentTeam().
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Fixes the denial of service advisory GHSA-8rr7-cvq3-gmfh (affected
versions >=1.5.0,<2.10.0). nette/schema moves to 1.3.6 with it.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
SSH_COMMAND_TIMEOUT=0 (or an invalid value) removed the time limit of every
remote command, and imports could then be marked stale while still running.
Use the default of 3600 seconds for values that are not positive.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
resend-php 1.x reads the statusCode of Resend errors and knows more error
names. Coolify only uses emails->send and the exception classes, which did
not change.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The duplicate check used the signed-in user's current team, or all teams
when there was none. Check the team the key belongs to.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GitHub, Gitea and Bitbucket manual webhooks accepted any signature when
APP_ENV=local. GitLab and the GitHub App webhook already did not.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A 200 response with an empty body or an error page replaced the local
templates and the shared cache, so the service list became empty.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
resend-php 0.18 reads a "code" key that the Resend API does not send
(it sends statusCode), so every Resend error failed with Undefined array
key "code". Errors with names that the SDK does not know were returned as
a normal response and counted as a sent email. Read statusCode and name from
the error, and treat a response without an email id as an error.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Add a Lima backend to scripts/dev. Auto mode picks it on macOS when
limactl (Lima >= 2.0) is installed, and COOLIFY_DEV_SERVER_BACKEND=lima
forces it. Lima VMs are named coolify-dev-<slot>-<profile>. Coolify
connects to them through Lima's forwarded SSH port on
host.docker.internal, and only guest ports 80/443 are forwarded to
the host.
dev:qemu:seed now takes --ip and --port options, so the seeded server
can use a forwarded SSH address instead of the profile IP.
Add tests for the Lima start flow and for seeding with an overridden
address, and document the macOS setup in AGENTS.md.
- Configuration diff: report all settings when an app has no earlier
snapshot, so older apps show "configuration changed" again (#11430).
- DigitalOcean/Vultr: do not repeat the "API error:" prefix.
- HasSecretManager: the deleting listener returns nothing, so later
deleting listeners still run.
- GitLab webhook: use new_public_id() like the other webhooks.
- Team danger zone: show the link and open the page only for the team owner.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>