Commit Graph
17532 Commits
Author SHA1 Message Date
Andras BacsaiandClaude Opus 5.5 6bc083eee6 test(server): fake sleep in the functional recheck tests
The connection check now retries SSH after a 3 second delay.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:30:46 +02:00
Andras Bacsai b5393e24ec fix(server): recheck unreachable servers before failing deployments
Deployments failed with "Server is not functional" when the server was
still flagged unreachable after a single failed scheduled check. Add
Server::isFunctionalAfterRecheck(), which runs a live SSH and Docker
connection check when the server is marked unreachable. It skips the
check for disabled servers and servers with placeholder IPs, then
re-evaluates isFunctional(). ApplicationDeploymentJob now uses it.

ServerManagerJob no longer skips the SSH connection check for servers
with a live Sentinel when the server is flagged unreachable or unusable.
The Sentinel heartbeat does not restore these flags, so without the
check they would never recover.

Add feature tests for the recheck behavior and unit tests covering the
Sentinel skip condition.
2026-10-01 09:30:21 +02:00
Andras BacsaiandClaude Opus 5.5 69c809f4a2 fix(server): stop marking servers offline after one failed check
A single failed SSH attempt or any unrelated exception in
ServerConnectionCheckJob marked the server unreachable and unusable, so
deployments failed with "Server is not functional" while the server was
online.

- Retry a failed SSH attempt once after 3 seconds within the same check.
- Mark the server unreachable only after UNREACHABLE_THRESHOLD (2)
  consecutive failed checks; notifications use the same threshold.
- Keep the stored state when the job fails for a reason other than SSH or
  Docker (database, cache, bugs).
- Raise the job timeout to 60 seconds and the overlap lock above it. The
  old 15 second timeout killed checks before they wrote a result.
- Run the Docker and Compose version commands once with a 15 second
  timeout. A failed Compose check no longer marks the server unusable.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 09:30:19 +02:00
Andras Bacsai bcc07dd47e feat(github-runners): allow public repositories and refuse pull request jobs by default
The runner group was limited to private repositories, so jobs from
public repositories never got a runner: Coolify still started a runner
for each matching job, which then waited idle until its timeout.

The runner group now allows every repository of the organization,
public ones too; existing private groups are updated. GitHub gives a
runner any queued job with matching labels, so pull request jobs are
refused inside the runner: a job-started hook fails jobs from
pull_request, pull_request_target and review events before any step
runs, and fails closed when the event is unknown. A new runner setting,
off by default, allows pull request jobs; the settings page then warns
to require approval for fork pull request workflows on GitHub.
2026-10-01 08:14:30 +02:00
Andras Bacsai d1cf3e6ef1 fix(secrets): keep the secret manager source when cloning resources
Cloning an application, database, service or environment did not copy
the secret manager link, so variables with remote secret references
failed on the next deployment with "no secret manager source is
configured".

Clones now get a copy of the link when the integration token belongs to
the clone's team. Server transfer does not export integration tokens or
links; the export now warns which resources need the token and link
again on the target instance.
2026-10-01 07:16:45 +02:00
Andras Bacsai 8d7469230c fix(secrets): skip remote secret names that are not valid variable keys
Import all stopped at the first remote secret whose name is not a valid
environment variable key (for example db-password in Vault or
Infisical). The key setter threw, so the import was partial, no audit
entry was written, the variable list did not refresh, and the user saw
only a generic error.

Invalid names are now skipped and listed in the result toast and the
audit entry, and the rest are imported. Browse marks invalid names and
offers no add action for them, and adding one shows a clear error. The
refreshEnvs event now goes only to the environment variable list.
2026-10-01 07:16:45 +02:00
Andras Bacsai 8144b1cbc1 fix(transfer): keep SQLite volume links during server transfer
The server transfer bundle did not include the standalone_sqlite_id of
persistent volumes, so after an import an application's mount of a
SQLite data volume was no longer linked to the database. The shared
volume checks then failed: deleting the SQLite database, or the
application with its volumes, could remove the SQLite data.

The exporter now sends the SQLite database uuid with each volume, and
the importer links the volume again after all databases are imported
(with a warning when the database is not in the bundle). As a backstop,
StandaloneSqlite::deleteVolumes() skips volumes that another resource
uses, like Application already does.
2026-10-01 07:16:35 +02:00
Andras Bacsai 658ac6bca5 fix(database): check MongoDB health with ping
The MongoDB health check passed only on the writable primary
(db.hello().isWritablePrimary or isMaster().ismaster). A replica set
that was never initiated, which is common for Prisma setups, and any
non-primary member were reported unhealthy after a restart.

The check now runs db.adminCommand({ ping: 1 }) with mongosh or the
legacy mongo shell. Ping needs no authentication, passes on every
running member, and still fails when mongod is not reachable.
2026-10-01 07:16:27 +02:00
Andras Bacsai 13a57cc059 fix(webhooks): never lock out manual webhook deliveries with a valid signature
The failed-authentication lockout for manual webhooks was checked before
the signature, and the bucket used the client IP from X-Forwarded-For.
An unauthenticated client could send failed attempts with the Git host's
address for a public repository and branch, and correctly signed
deliveries then got 429, so deployments were lost.

The lockout now applies only to deliveries that fail authentication; a
delivery with a valid secret for a matched application is always
processed. Failures are counted per connection address (REMOTE_ADDR, or
CF-Connecting-IP on cloud), so rotating X-Forwarded-For no longer gives
a new bucket.
2026-10-01 07:16:27 +02:00
Andras Bacsai 7ef7fee59a fix(proxy): accept Docker Compose merge tags in proxy configurations
Proxy port validation parsed the configuration with plain Symfony YAML,
which rejects Docker Compose tags such as !reset and !override. Proxy
start, restart and save then failed for these valid configurations, and
automatic start after a reboot stopped silently. v4.3.23 did not block
on such configurations.

The parser now reads custom tags and validates the values inside them,
so malformed ports are still rejected. CheckProxy refreshes the status
of a running proxy before it reads the configuration, as in v4.3.23, so
an unreadable configuration no longer hides a running proxy.
2026-10-01 07:16:26 +02:00
Andras Bacsai cd8d299955 fix(deploy): never build the image on additional servers
An additional server of a multi-server application must run the image
that the main server built and pushed. On servers with the build or
both role, a disabled build cache still forced a local rebuild, and a
failed registry pull fell back to a local build. That ignored the build
server and the build-server fallback setting, and the servers could run
different images.

Additional servers now always pull the main server's image (a forced
rebuild is reset, except for Docker Compose applications, which build
on each server by design). When the image is not in the registry, the
deployment fails with a clear message instead of building it.
2026-10-01 07:16:14 +02:00
Andras Bacsai 766fe56448 fix(deploy): remove the new container when a rolling update fails
Since rolling updates also run for applications with a custom internal
name (when consistent container names are off), the new container gets
a generated name. failed() still kept the new container whenever a
custom internal name was set, so an exception after the new container
started (for example an SSH or docker inspect error during the health
check) left it running next to the old version with the same proxy
labels.

failed() now keeps the container only for consistent container names
and pull request deployments, the same cases that skip rolling updates.
2026-10-01 07:16:13 +02:00
Andras Bacsai 014e3c29b1 fix(storages): clarify volume deletion steps for driver options
Reword the legacy-volume notice to say the resource must be stopped and
data backed up before deleting the entry and Docker volume.

In the delete modal, explain what happens with and without deleting
the Docker volume. Add a warning that the resource must be stopped
first, because Docker cannot remove a volume that a running container
uses, and suggest a backup before deleting.
2026-09-30 22:14:02 +02:00
Andras Bacsai 3c23e2d30f refactor(auth): remove OAuth re-authentication for destructive actions
Drop the OAuth confirm/callback flow that let users confirm destructive
actions by re-authenticating through a linked provider. This removes the
confirm route and session handling in OauthController, the middleware
bypass for those routes, OauthSetting::providerName(),
User::oauthConfirmationProviders(),
OauthLoginService::identityBelongsToUser() and the
socialite_reauthentication_parameters() helper.

Users with a linked OAuth identity no longer need a password step and
only use the dialog's typed confirmation. Password confirmation is now
required only for users who have a password and no SSO identity. The
confirmation modal and tests are updated to match.
2026-09-30 21:37:02 +02:00
Andras Bacsai b13fa4dad0 fix(database): choose the MongoDB health check shell at run time
The health check from #11730 picked mongosh or the legacy mongo shell
from the image name (mongo:4*). Images without mongosh whose name does
not start with mongo:4, such as mongo:3.6 or registry mirrors of
mongo:4.4, ran a missing mongosh (exit 127) and stayed unhealthy.

The check now uses mongosh when the container has it and falls back
to the legacy mongo shell otherwise.

Replace the test that only checked the source text with tests that run
the generated script with fake shells.
2026-09-30 20:48:54 +02:00
Andras Bacsai 0604101781 Merge pull request #11730 from cbratschi/codex/fix-mongodb-healthcheck
fix(database): use MongoDB writable-primary health check
2026-09-30 20:45:48 +02:00
Andras Bacsai 284adede6c fix(ssl): issue P-256 server certificates so Electron clients connect
Server certificates now use a prime256v1 (P-256) EC key. The CA keeps
secp521r1. BoringSSL-based Electron clients such as MongoDB Compass
cannot use a P-521 server key.

Add a feature test that checks the CA stays on P-521 and the server
certificate uses P-256. It also checks that the server key matches its
certificate and that the CA signature verifies.
2026-09-30 20:31:33 +02:00
Andras Bacsai 5a2006e99c perf(docker): look up foreign preview owners once per container status check
GetContainersStatus now resolves all unknown preview owners with one query,
like the Sentinel push. The lookup helper also no longer fails on an
application without a uuid.
2026-09-30 19:23:35 +02:00
Andras Bacsai 7183e52779 fix(deploy): never force start the same deployment twice
Force start now uses the locked start path. It still skips the concurrency
limits, but a deployment that is no longer queued is not started again, and
the deployment page says so.
2026-09-30 19:23:35 +02:00
Andras Bacsai 0ab19998d5 fix(dns): send only a public server IP to Cloudflare Domain Connect
Domain Connect used the unfiltered server IPs, so a private address could be
published. It now uses the first public IP and refuses with the no public IP
message when there is none, like automatic DNS.
2026-09-30 19:23:35 +02:00
Andras Bacsai e8337c73eb chore(release): set the version to 4.4.0
The release workflow only accepts vX.Y.Z tags that match the config version.
The nightly versions.json now matches versions.json, as the workflow test
requires.
2026-09-30 19:23:35 +02:00
Andras Bacsai a2cd6351b2 fix(databases): keep SSL certificate files and CA in sync with each engine
- Regenerate SSL Certificates now writes server.crt/server.key for every
  engine except MongoDB, which keeps the combined server.pem. Before, the
  button always wrote server.pem, so Postgres, MySQL, MariaDB, Redis, KeyDB
  and Dragonfly failed to start after a regenerate.
- The certificate renewal job uses the same rule, so MongoDB no longer
  switches to server.crt/server.key on renewal and fails on restart.
- Starting an SSL database writes the CA that signs its certificate to
  /data/coolify/ssl/coolify-ca.crt, so a stale CA file (for example one
  written by another server entry for the same host) no longer causes
  "certificate signature failure".
- Saving a custom CA certificate that does not match the stored CA
  private key is rejected.
- The CA file write commands live in SslHelper::caCertificateFileCommands().

Fixes #10388
Fixes #6771
Fixes #11926
2026-09-30 19:01:11 +02:00
Andras Bacsai c221b22503 fix(ui): clarify dialogs, use a red focus ring on error buttons, fix missing icons
- The certificate delete dialog no longer lists a proxy restart it does not
  perform, and its warning mentions the backup.
- Confirmation dialogs list actions with a dash instead of a trash icon.
- Error buttons get a red focus ring instead of the accent ring.
- Use existing icons for the TLS empty state and built-in shared variables.
2026-09-30 18:59:49 +02:00
Andras Bacsai 8b007edceb fix(database): skip MySQL system databases when restoring all databases
An all-databases MySQL/MariaDB restore also restored the mysql system
database, so after the next restart the root and app passwords were the
source ones and the MariaDB health check failed. The restore now skips mysql,
sys, performance_schema and information_schema by default. The new option
"Restore users and privileges" (restore_mysql_users in the API) keeps the
old behaviour.
2026-09-30 18:59:49 +02:00
Andras Bacsai 93507f7699 ci: remove the manual ARM64 branch workflow
It could overwrite the :next and :main tags with an arm64-only image.
2026-09-30 18:10:18 +02:00
Andras Bacsai a7dee08cdd chore: drop the activity command cleanup migration
Old plain-text commands are removed by the daily 60-day activity_log cleanup.
2026-09-30 18:10:18 +02:00
Andras Bacsai 4f8bf46feb fix(auth): remove the unused Fortify profile route that bypassed the SSO email lock
PUT /user/profile-information changed the email with no SSO check and no
verification. The profile page has its own verified email change, so the
Fortify feature, its action and its binding are removed.
2026-09-30 18:10:18 +02:00
Andras Bacsai b1399d97df test: lower the content volume minimum to the current templates
The signoz and plausible template updates changed the count from 94 to 91.
2026-09-30 18:10:10 +02:00
Andras Bacsai df943b4828 fix(api): return 422 for a non-string custom_container_name_prefix 2026-09-30 18:10:10 +02:00
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