A merge brought back ghcr.io as the default registry and helper image
registry. The install and upgrade scripts and the compose files use
docker.io.
- Default REGISTRY_URL and the helper image registry to docker.io
A pull request id above 2147483647 caused a database error and a 500.
- Previews store: validate max 2147483647 (422)
- /deploy: return 400 for pr or pull_request_id out of range
- Preview show, logs, update, and delete: return 422 for ids out of range
- Document the maximum in the OpenAPI spec
Container names use custom_internal_name only when consistent container
naming is on. The API accepted the name and then ignored it.
- Return 422 with a message that says to set
is_consistent_container_name_enabled to true
- Accept the name when the request or the application turns the setting on
- Document the requirement in the OpenAPI spec
An import with source=server reads a file at an absolute path on the
server, but it needed only the write ability.
- Return 403 without the deploy or root ability, the same as other host
paths outside the resource directory
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
When a container that a volume backup stopped no longer existed (for
example after a redeploy), recovery kept it as remaining. The execution
needed attention and every later run of the schedule was skipped.
- Treat "no such container" or "no such object" from docker inspect as
recovered; other failures still need attention
When the execution of the next database could not be created, the
error handler used the state of the previous database. It marked the
successful backup of that database as failed and could delete its file.
- Reset the execution, location, and output at the start of each database
The service start script goes to the server over stdin. When the
configuration of an existing volume changed, docker compose asked to
recreate the volume and read the next script line as the answer. That
line, for example the proxy network connect, did not run.
- Add < /dev/null to the service compose up and pull commands
- Do not use --yes, because it recreates the volume and deletes data
ReconcileGithubRunnersJob connected over SSH every minute to every
server with a runner configuration, also when the runners were
disabled.
- Reconcile a server only when its runners are enabled or it still has
active runner executions, so jobs that run after disabling still end
failed() marked the latest running execution of the server as failed.
A manual run that never started, or a run that timed out, could mark a
different running cleanup as failed.
- Keep the execution id in the cache under the queue job uuid, because
failed() runs on a fresh copy of the job
- Change nothing when the job created no execution
A manual cleanup that started while a stop-triggered cleanup held the
server lock was dropped. The UI said it was queued, but it never ran.
- Release a manual run and try again after 60 seconds
- Stop the retries 780 seconds after dispatch (the lock expiry plus a
margin)
- Scheduled and stop-triggered runs are still dropped
CleanupInstanceStuffsJob, CleanupOrphanedPreviewContainersJob, and
CheckMissingDatabaseBackupsJob had no uniqueFor. A lock left by a
stopped worker blocked them for up to 24 hours.
- Set uniqueFor to 60, 600, and 1800 seconds
- Give CheckMissingDatabaseBackupsJob a 1800 second timeout, so a run
cannot outlive its lock
RevalidateUnusableS3StoragesJob had no uniqueFor. A lock left by a
stopped worker blocked the job for up to 24 hours, so unusable S3
storages were not checked again and their backups were skipped.
- Set uniqueFor to 1800 seconds
When a deployment on the primary server finished, deployments of the
same application queued on an additional server did not start. They
waited for the primary deployment, and then no queue event came for
their server.
- Also advance every server that has a queued deployment of the
application; the per-server and per-application limits still apply
When a deployment on an additional server finished, only the queue of
the primary server advanced. Deployments queued on the additional server
could wait with no limit.
- Also advance the queue of the server of the finished deployment
When the job constructor or the dispatch threw (for example, an invalid
registry image name), the deployment stayed in_progress. It blocked
later deployments of the application and used a build slot.
- Mark the deployment failed with fail_undispatchable_deployment()
- Throw the error again, so the UI and API still show it
Jobs that v4.3.23 put in the queue have no type property. The promoted
constructor property had no default, so these jobs failed with "must not
be accessed before initialization" after the upgrade.
- Declare type as a normal property with a null default
Service compose and .env content was sent inline in the SSH command.
Linux limits one argument to 128 KiB, so a service with a generated
compose over about 96 KB failed to save or start with "Argument list
too long".
- Add instant_remote_write_file(), which streams content to a remote
tee over SSH stdin (sudo tee for non-root SSH users)
- Add SshMultiplexingHelper::generateSshStdinCommand() with the same
SSH options as generateSshCommand()
- Write the service compose and .env with the new helper
The container lookup matched the pullRequestId label as a substring, so
PR 1 also matched PR 12, 13, and 100. Deploying, closing, or deleting one
preview could stop and remove the containers of other previews.
The create endpoint checked $validator->failed(), which is always empty,
so its validation rules never ran. The start step also read
instant_deploy loosely, so a write-only token could send "false" and
still start the database.
- Use $validator->fails() so invalid input returns 422
- Read instant_deploy with $request->boolean() for the gate and the start
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.
On non-secure origins the copy button falls back to execCommand('copy')
with a temporary textarea. The textarea was appended to <body>, outside
the x-trap focus trap of the DNS entries modal, so the trap pulled focus
back and the selection was lost before the copy. The button showed
success, but nothing was copied.
Append the textarea next to the button, select it explicitly, and
restore focus afterwards. "Copy all" now uses the shared copyButton
handler instead of its own duplicate fallback that ignored failures.
Closes#12075
/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.
Raw commands, URLs and configuration blobs can contain credentials even
when their field names don't look sensitive. Add a list of these fields
to AuditEvent (git_full_url, build/start commands, database confs,
proxy configuration, compose files, environment variables, etc.). Expose
AuditEvent::isSensitiveField() so redaction and audit change capture use
the same check.
Auditable now excludes a model's class-level $hidden fields as well as
its runtime hidden fields. A field stays out of audit changes even after
makeVisible() is called on the instance.
Add regression tests for credentials embedded in raw fields, database
config on create/delete, nested metadata and proxy configuration, and
hidden fields after makeVisible().
Record old/new values for auditable create, update, and delete actions, persist them encrypted on audit events, expose them in the API, and let the team audit log expand a field-level before/after view. Sensitive, hidden, and identifier fields stay out of the change payload.