Commit Graph
17502 Commits
Author SHA1 Message Date
Andras Bacsai 71b1e825ef fix(ui): revert failed notification toggles and escape the 2FA setup URL once 2026-09-30 18:10:10 +02:00
Andras Bacsai 41eb524d3e fix(dns): compare IPv6 addresses by value 2026-09-30 18:10:10 +02:00
Andras Bacsai 487e86c493 fix(service): delete a service part even when its container removal fails 2026-09-30 18:10:10 +02:00
Andras Bacsai 62d861d23a fix(jobs): resume S3 revalidation and remove orphaned notification throttles
- The S3 revalidation job stops within its time budget and continues from a
  cursor on the next run.
- An hourly sweep deletes throttle rows whose subject no longer exists.
2026-09-30 18:10:10 +02:00
Andras Bacsai d8bbd8d196 fix(storage): resolve relative file paths, keep volume options on clone, use random clone dirs
- Resolve relative LocalFileVolume paths against the resource directory.
- Create cloned volumes with the source driver and options.
- Stage clone archives for non-root users in a mktemp directory (mode 700)
  instead of a predictable path in /var/tmp.
2026-09-30 18:10:10 +02:00
Andras Bacsai 6dc332c203 perf(sentinel): cache traffic per minute, skip unsafe keys, batch owner lookups
- Align the traffic window to the minute so the 60 s cache is used.
- Skip only an unsafe key instead of hiding the whole server.
- Look up unknown container owners in one query per push.
2026-09-30 18:10:10 +02:00
Andras Bacsai ad4d1d8e5d fix(database): fail expired queued starts instead of running them late
A queued start or restart whose reservation expired no longer runs. Failed
queued starts are recorded as failed activities and release the reservation.
2026-09-30 18:10:10 +02:00
Andras Bacsai f8f2945aec fix(deploy): start queued deployments atomically and report cancelled multi-server runs
- Decide whether to start a queued deployment inside the application and
  server lock, so two requests cannot start the same app twice.
- Send the multi-server summary when the last child is cancelled.
- Show the registry message when an inline Dockerfile image is missing on a
  deployments-only additional server.
2026-09-30 18:10:10 +02:00
Andras Bacsai ce32bff820 fix(proxy): accept Compose variables in proxy ports like v4.3.23
Entries such as ${HTTP_PORT:-80}:80 blocked proxy start, save and restart.
Defaults are used for the conflict check and unresolved entries are skipped.
2026-09-30 18:10:10 +02:00
Andras Bacsai ef90fe06f1 feat(proxy): back up acme.json before changes and allow restoring backups
Coolify copies acme.json to a timestamped backup before every change and
keeps the last 10. The proxy page lists the backups with Restore and Delete
for team admins, and the restore dialog can restart the proxy right away.
2026-09-30 18:10:10 +02:00
Andras Bacsai f53a883a60 fix(secrets): require the secret again when a secret manager connection changes
Changing the Vault base URL or namespace, or the Infisical base URL or
client ID, sent the stored secret to the new settings. The secret must now
be entered again, and base URL changes are audit logged.
2026-09-30 18:10:10 +02:00
Andras Bacsai 9a4469a42d fix(team): keep users of other teams when an invitation is revoked or expires
A user who accepted another team's invitation but had not set a password
yet was deleted, with their team data. Now only users in no team except
their own personal team are deleted.
2026-09-30 18:10:10 +02:00
Andras Bacsai b8bc197b17 fix(oauth): key non-OIDC identities by provider instance
GitLab, Authentik, Zitadel, Clerk and Azure identities were keyed by the
provider name only, so a user with the same subject on another instance
could sign in as the linked user after the base URL or tenant changed.
The issuer column now stores the normalized instance, existing rows are
backfilled from the current settings, and oauth:rekey handles host renames.
2026-09-30 18:10:10 +02:00
Andras Bacsai 5921fb2958 fix(compose): keep name-only declaration for pre-existing volumes
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
2026-09-30 14:31:33 +02:00
Andras Bacsai 9e7cb60b0d feat: add import restore options, OAuth confirmation, and fixes
- 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
2026-09-30 13:35:22 +02:00
Andras Bacsai d1bdecba6d fix(compose): allow variables in external volume names
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.
2026-09-30 12:29:50 +02:00
Andras BacsaiandClaude Opus 5.5 09af3e065a ci(build): use rolling tags for main image builds
Pushes to main published three tags per commit (sha-<sha>,
sha-<sha>-amd64, sha-<sha>-aarch64). Publish the per-arch images under
rolling main-amd64/main-aarch64 tags instead, and keep one sha-<sha>
manifest per commit next to a rolling main tag.

Serialize runs so concurrent pushes cannot mix architectures, and label
images with the source revision.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:21:26 +02:00
Andras BacsaiandClaude Opus 5.5 fd85b740f5 feat(github-runners): add ephemeral self-hosted runners on build servers (#12054)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:13:11 +02:00
Andras BacsaiandClaude Opus 5.5 03a056709d feat(server): add Docker registry logins page with per-server login (#12048)
Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-30 12:11:37 +02:00
Andras Bacsai f622213988 fix(git-source): follow same-origin redirects for Git provider APIs
Git source HTTP clients previously disabled redirects entirely, so API
calls for renamed repositories (e.g. GitHub 301 to /repositories/{id})
failed. Add SafeExternalUrl::sameOriginRedirectOptions() and use it in
the GitSource HTTP macro to follow up to five strict redirects that stay
on the same scheme, host and port.

A redirect to a different origin gets a RequestException, because the
DNS pin covers only the original host and port. This blocks SSRF via
redirects to metadata or attacker hosts. Authorization headers and the
original request method and body are kept on same-origin redirects.

Tests cover following same-host redirects, keeping POST on a redirect,
refusing cross-host/scheme/port redirects and the five-redirect cap.
2026-09-30 11:40:36 +02:00
Andras Bacsai c70463fb9f fix(env): hide hardcoded compose values from users who cannot edit
Hardcoded variables parsed from docker-compose are now redacted in the
environment variable list unless the user can update the resource. The
value is nulled server-side and flagged with is_value_hidden, so it no
longer reaches the Livewire snapshot or rendered HTML.

ShowHardcoded gets a locked isValueHidden property. When it is set, the
copy button is hidden and the details modal shows "Hidden (only admins
can view)" in place of the value. Previously the view checked member
role only.

Add tests covering redaction for members and visibility for owners.
2026-09-30 11:31:29 +02:00
Andras Bacsai e2c826a976 fix: run SSH wrapper via sh and scope secret tokens to resource team
SSH: sshd runs the remote command with the user's login shell, which
can be fish or csh/tcsh. Those shells cannot parse POSIX `if ... fi`, so
the bash/sh fallback wrapper now runs inside `sh -c "..."`. Unit tests
run the wrapper under every login shell that is installed and check that
it stays one simple sh command.

Secret managers: the SecretManagerLinks component now lists and resolves
tokens by the resource's team instead of the session team. A team switch
in another tab can no longer link a token from a different team.
SecretManagerLink::fetchSecrets() also throws when the linked token does
not belong to the resource's team, so no request is sent with it.

Also adds a lesson note: remove the SSH mux file before live SSH or
login-shell tests.
2026-09-30 11:05:42 +02:00
Andras Bacsai 6a59008cda fix(dns): lock DNS modal state and unquote modal confirmation args
Mark DNS proposals, conflicts and server IP properties as #[Locked] so
clients cannot tamper with them. Re-resolve the zone per hostname,
lowercase hostnames, validate record content server-side and use the
resource's team id for DNS lookups and jobs.

Move modal-confirmation submitAction parsing into a tested JS helper
that strips quotes and keeps commas, parentheses and escaped quotes
inside quoted arguments. Pass the hostname to replaceManagedDnsRecord
through it instead of Js::from.

Rework member-permission tests to open the real DNS dialog rather than
setting locked properties directly.
2026-09-30 10:23:51 +02:00
Andras Bacsai 2b5bffe37d fix(service): delete resources from Coolify when server is unreachable
DeleteResourceJob no longer restores a service and rethrows when remote
cleanup fails. The resource is always removed from Coolify, and the team
gets a notification that Docker resources may remain on the server. No
notification is sent when the server itself was already deleted.

removeSubresourceContainer now returns false instead of throwing when the
server is not functional. Deleting a service application or database then
shows a warning that the container is removed on the next service start.

StopService handles a missing destination server, environment, project,
or team without throwing. The danger zone warning now shows for any
resource whose server is unreachable, not only services.
2026-09-30 08:02:02 +02:00
Andras Bacsai 033f78d907 fix(backup): fail compressed dumps when the dump command fails
A plain pipe into the compressor only reported the compressor's exit
status, so a failed dump still produced a small, valid archive. Add
pipeToFileKeepingExitStatus() to propagate the source's exit status in
POSIX sh and use it for all compressed dump commands.

- Delete the partial local backup file on any failed backup, not only
  for ClickHouse, since untracked files are never removed by retention
- Wait up to 10s for locks when running VACUUM INTO for SQLite backups
- Refuse SQLite restores unless the file has a SQLite header, so an
  empty backup no longer wipes the database
- Add tests for dump exit status handling and SQLite restore validation
2026-09-29 23:23:02 +02:00
Andras Bacsai 2ac5969d90 fix(docker): stop matching containers by numeric resource id labels
Numeric coolify.{type}Id labels can collide with resources on another
Coolify instance managing the same server, so they no longer select or
resolve container owners. Drop the legacyId fallback from container
lookups in DeleteService and ComplexStatusCheck.

Legacy application containers without a UUID label are now resolved by
their compose service name, which starts with the application UUID.
2026-09-29 23:05:34 +02:00
Andras Bacsai 680f4e5d29 fix: use GitLab API v4 for OAuth and gate env copy by update policy
Socialite's GitLab provider fetches the user from /api/v3/user, which
GitLab removed in 11.0 (410 Gone). Add App\Auth\GitlabProvider that
reads /api/v4/user with a bearer token and register it in the
socialite helper, for both gitlab.com and self-hosted base URLs.

Environment variable copyValue now checks the 'update' policy on the
variable instead of the member role of the current team, so members
can no longer copy values after switching to a team they own. Remove
the redundant member check from ShowHardcoded.

Add tests for the GitLab v4 user lookup and the team-switch copy case.
2026-09-29 22:36:56 +02:00
Andras Bacsai f9e5a00312 fix(oauth): let pre-existing users link first identity without verification
Before OAuth identities existed, OAuth sign-in matched users by email
only. Add a created_before_oauth_identities flag, set for every user at
upgrade time, so their first OAuth identity links without a provider
email verification claim. The flag is cleared once an identity links.

Also trust Microsoft Entra ID user principal names and GitLab confirmed
primary emails as verified, and pass the email to hasVerifiedEmail.
Add tests using real provider payloads.
2026-09-29 22:11:06 +02:00
Andras Bacsai a599c9e8b7 fix(server): keep data directory commands working for non-root SSH users
A non-root SSH user cannot open redirects, expand globs, or cd into
/data/coolify (9999:root 0700). Several remote commands relied on that
and failed on non-root servers.

- Write README files with `tee` instead of shell redirects in database
  starts and application deployments
- Clear Postgres init scripts with `find` instead of a glob
- Split the proxy config backfill if/else so each branch gets sudo
- Start the log drain with `docker compose -f` instead of `cd`
- Wrap the preserved-repository compose start in `sh -c` for non-root
- Stage clone archives outside /data/coolify via StagesCloneArchives
- Extend the sudo parser and add non-root command tests
2026-09-29 20:46:54 +02:00
Andras Bacsai 8b8022bac4 fix(proxy): run proxy commands without cd for non-root SSH users
The proxy directory is 9999:root 0700 on the Coolify host, so a
non-root SSH user cannot cd into it, open redirects in it, or expand
globs there (#4255).

- Start/restart proxy with absolute `docker compose -f` and
  `docker stack deploy -c` paths instead of `cd $proxy_path`
- Write the Caddyfile via `tee` instead of a shell redirect
- Check for and prune config backups with `find` instead of globs
- Read the config size with `stat -c %s` instead of `wc -c <`
- Add feature tests for non-root proxy commands and record the lesson
2026-09-29 19:16:50 +02:00
Andras Bacsai 3724e52f13 chore: remove stray agent worktree submodule reference
Delete the accidentally committed .claude/worktrees/agent-a1f84dd00720572e0
gitlink, which pointed to a local agent worktree rather than a real submodule.
2026-09-29 13:07:48 +02:00
Andras Bacsai e7465000b3 fix(api): key unauthenticated rate limits by Cloudflare client IP
Use auth_rate_limit_ip() rather than $request->ip() for the api, 5 and
feedback rate limiters. On cloud, each client behind Cloudflare now gets
its own bucket instead of sharing the proxy's bucket. Self-hosted
instances ignore the CF-Connecting-IP header.

Add feature tests for per-client sentinel push buckets on cloud and for
ignoring the header on self-hosted.

Add scripts/restore-backup-vm.sh, which restores an instance backup into
an offline KVM VM to test database migrations. Bump the v4 version in
other/nightly/versions.json to sha-1e9add3.
2026-09-29 13:06:44 +02:00
Andras Bacsai 99c840922c fix(notifications): throttle repeated notifications per resource
High disk usage notifications were sent every ~10 minutes on Coolify Cloud
because the cache rate limiter did not hold across Sentinel-triggered checks.

- Add a notification_throttles table and NotificationThrottle helper that
  atomically records when a notification was sent for a resource.
- Add the ThrottledNotification contract; Team::notify() skips a throttled
  notification that was already sent within its interval.
- High disk usage: configurable per-server interval
  (server_disk_usage_notification_interval_hours, default 24h) in
  Server > Advanced and the servers API.
- Container restarted (coolify-proxy, database TCP proxy): at most one
  notification per hour per server/database during a restart loop.
- Move the unreachable and missing-backup markers into the new table and
  migrate existing data (servers.unreachable_notification_sent and
  scheduled_database_backups.missing_backup_notification_sent_at).
- Regenerate OpenAPI files.
2026-09-29 11:29:39 +02:00
Andras Bacsai 49086c82a8 fix(multi-server): block additional servers with a mismatched proxy (#12053) 2026-09-29 11:10:22 +02:00
Andras BacsaiandClaude Opus 5.5 3d763c1577 refactor(server-transfer): always claim on import and drop remote claim file
Import now always claims the server and rebinds Sentinel. Remove the
SSH-written ownership file, the write_remote/rebind_sentinel/claim
options, and the POST /servers/{uuid}/claim endpoint. The migrator now
fails when the target cannot take management. Disabling management
records transfer.management_disabled so the server shows as
transferable, and the transfer and import pages are simplified.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:56:11 +02:00
Andras BacsaiandClaude Opus 5.5 1e9add3b42 fix(ui): show validation checkpoints as a vertical list
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:54:57 +02:00
Andras BacsaiandClaude Opus 5.5 cbee84353e refactor(events): broadcast team events to an explicit team
Move the shared team id and team channel logic of 15 broadcast events
into a BroadcastsToTeam trait. Events no longer fall back to the session
team: the session team can differ from the resource team, and queued
jobs have no session.

Fix StopDatabaseProxy, which dispatched DatabaseProxyStopped without a
team, so the event was never broadcast from queued jobs.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:46:16 +02:00
Andras Bacsai 65262e639e fix(installer): keep resource data ownership on reinstall and upgrade
install.sh ran chown -R 9999:root and chmod -R 700 on all of
/data/coolify on every run. This changed bind-mounted data of services,
applications, and databases, and reset the ownership that a non-root
SSH user needs.

Set owner and mode on /data/coolify and the resource directories only
when the installer creates them. Change only source, ssh, and images
recursively, because the coolify container uses them.

Also wait until the Docker daemon accepts connections after a restart,
because OpenRC returns before the daemon is ready.

Fixes #11945
2026-09-29 10:37:54 +02:00
Andras BacsaiandClaude Opus 5.5 3452faec87 fix(livewire): bind team broadcast listeners to the mounted team
Livewire rebuilds listener names from the session team on every event
call. After a team switch in another tab, open pages still listen on the
old team channel, so events failed with EventHandlerDoesNotExist (500),
or ran handlers that load resources scoped to the new team (404).

Store the team at mount and build team channel listeners from it. Events
for a team that is no longer the session team are ignored.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:31:32 +02:00
Andras Bacsai aae1e0c0e5 fix(ui): show Cloudflare token link as a button in integration token form 2026-09-29 10:22:07 +02:00
Andras Bacsai 5ecc689135 fix(help): show an error when feedback cannot be delivered
Without SMTP or Resend configured, the Help form posts to the Cloud
feedback API but ignored the response, so it always reported success.
Check the response and show an error on failure. The Cloud endpoint now
returns 502 when the Discord webhook fails.
2026-09-29 10:19:57 +02:00
Andras BacsaiandClaude Opus 5.5 639a07e4cb feat(s3): revalidate unusable S3 storages hourly
Re-test S3 storages marked unusable every hour so a storage that becomes
reachable again is usable without manual action. Failures keep the
storage unusable and reuse the existing once-per-outage email.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-29 10:16:09 +02:00
Andras Bacsai 452259aacc feat(scheduler): dispatch scheduled jobs by their stored next run time
The scheduled job manager checked every enabled schedule each minute and
evaluated its cron expression in PHP. On Coolify Cloud one run took more than
60 seconds, so runs overlapped and minutes were skipped.

Each schedule now stores its next due time:
- next_run_at on scheduled database backups, scheduled tasks, and volume
  backups, and docker_cleanup_next_run_at on server settings (UTC, indexed)
- model hooks calculate it when a schedule is created or its frequency or
  enabled flag changes; a server timezone change or a move to another server
  calculates it again for the affected schedules

The `scheduled:dispatch` command runs the manager every minute from the
scheduler, not from a queue. It selects only due rows and claims each
occurrence with an atomic update of next_run_at, together with its delivery
record, so parallel runs on several nodes never dispatch it twice.
- database and volume backups run once when they are late; tasks and Docker
  cleanups more than 10 minutes late are logged as missed
- a local time that occurs two times when the clocks go back runs once
- the migration continues each schedule after its last recorded occurrence and
  drops scheduled_job_states

Show the next run, in the server timezone, on the database backup, scheduled
task, volume backup, and Docker cleanup pages, or "Invalid frequency" when the
frequency cannot be calculated. scheduled:diagnostics now lists overdue
schedules and open deliveries.

Remove workarounds that are no longer needed: the queue overlap lock and its
stale-lock repair, the catch-up window, and debug-only logs.
2026-09-29 10:07:23 +02:00
Andras Bacsai 2928a27966 fix(docker): pass the application to the container lookup in scheduled tasks
af983a29d changed getCurrentApplicationContainerStatus() to take the
application model, but missed this caller. Scheduled tasks of an
application failed with a TypeError.
2026-09-29 09:59:51 +02:00
Andras Bacsai 0393d4e931 fix(ui): move deployments indicator into the sidebar
The floating bottom-left pill and its expanded list covered page content.

- Desktop: show a compact 'N deployments' row in the sticky sidebar footer
  (spinner with count badge when the sidebar is collapsed); the list opens
  to the right of the sidebar
- Mobile: show a spinner with the count in the top bar; the list opens
  below the top bar
- Poll only the copy visible at the current breakpoint (wire:poll.visible)
- Show the indicator on the dashboard too, and drop the path-based
  visibility logic and its navigate round trip
2026-09-29 09:58:54 +02:00
Andras Bacsai af983a29d8 refactor(docker): identify containers by resource UUID instead of numeric id
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.
2026-09-29 09:57:56 +02:00
Andras Bacsai abf9915995 chore(deps): remove Laravel Nightwatch integration
Drop the laravel/nightwatch package and its agent service from the
development and production s6-overlay configs. Remove the related
environment variables, constants, phpunit setting, Boost skill
registration, and configure-nightwatch agent skills. Update the
container role script test to stop expecting the nightwatch-agent
service.
2026-09-28 21:16:11 +02:00
Andras Bacsai a1fe4b71d9 fix(deploy): write missing content files before compose up on any server
Call write_missing_content_files() at the start of start_by_compose_file()
so bind-mounted content files exist before `docker compose up` runs, which
would otherwise create them as empty directories. This also covers
Dockerfile deployments to additional servers that lack the files.

Add a feature test for a Dockerfile deployment to an additional server,
and note in lessons that long-lived Horizon workers must flush static
caches in Queue::before.
2026-09-28 17:04:01 +02:00
Andras Bacsai d28bc91ff5 fix(scheduler): read fresh server state in each job and log scheduling decisions
Horizon workers kept Server::findCached() results between jobs, so scheduled
database backups used stale reachability and subscription state. A backup that
was skipped with the stale state counted as done for that day. Flush the
Server identity map before each queue job.

Add debug logs for the full path of a scheduled run, so a multi-node cloud
instance shows where a run is lost:
- manager start delay, gap since the previous start, host, pid, memory, and
  a warning when the start is later than the catch-up window
- skip logs with the server state that was used and the current database state
- occurrence enqueued, claimed, rejected, completed and failed, with host and
  the delay since the due time; a warning when the previous run is still open
- count of occurrences still open after 15 minutes on each manager run
- database backup runs that end without a backup
- server connection state changes with the checking node and the SSH error

Also keep publishing pending occurrences when one of them fails.
2026-09-28 16:15:00 +02:00
Andras BacsaiandClaude Opus 5.5 86402ef926 fix(ui): clean up the server rows of multi-server applications
- Show the status summary next to the server name on the primary and additional server rows.
- Move Deploy, Make primary, Stop and Remove into one Actions dropdown per server row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-28 15:39:00 +02:00