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
- A dispatch failure for one additional server no longer skips the
other servers or the combined notification
- After a deployment fails to dispatch, the queue starts the next
queued deployment; nested queue advances run in a loop, not as
recursion, so repeated failures end
Since 17ca63ff4 every deployment except Docker image apps failed on a
build-time variable with a name such as MY-VAR, also restarts and
deployments that reuse an existing image. v4.3.23 failed only when it
built an image.
- Check build-time names right before the build steps; the start of
the deployment only logs a warning
- Names that break the .env file still fail every deployment
- Leave invalid names out of the build secrets helper container, so
restarts with build secrets work
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
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.
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.
Add StandaloneDocker::proxyConnectCommand() and run it inline when a
destination is created, replacing the synchronous
ConnectProxyToNetworksJob dispatch. Deployments also run it before
docker compose up, so the proxy is attached to the destination network
even when it was missing.
- Manual webhooks (GitHub, GitLab, Gitea, Bitbucket) now reject all
deliveries for a locked-out scope before reading secrets or verifying
signatures, including correctly signed ones, until the window expires.
- ProvisionGithubRunnerJob checks whether the execution has already
finished before and after each provisioning step, and only sets
ready_at or Failed while it is still occupying/provisioning. A runner
that is cancelled, timed out or completed mid-provisioning is no longer
started, retried or overwritten.
- VolumeCloneJob creates bind-volume clones as plain local volumes
without the source driver options, so the clone never mounts the
source host directory.
- Image pulls for additional servers no longer ignore docker pull errors.
- Add tests for each change.
Older versions accepted any static image through the API. Such a stored
value now stops the deployment with a message that lists the supported
web servers instead of a ValueError.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Write remote secret values as exact dotenv literals in the service .env
file and escape "$" where they go directly into a generated compose file
(environment, start commands, healthchecks).
- Resolve remote secrets before a service or database restart stops the
running resource, and show the secret manager error instead of a generic
start failure (new RemoteSecretException).
- Cache fetched key names for the autocomplete and rate limit how often a
user can contact the secret manager.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A queued deployment whose commit no longer validates (for example one
queued before an upgrade) made the job dispatch throw. The row stayed
in_progress, blocked every later deployment of the application, and
stopped the queue loop. Such a row is now marked failed with a log
entry and a failure notification, and the next queued deployment
starts.
An exception after the new container passed its health check (for
example in additional destinations, the next-deployment loop, or
notifications) made failed() remove the new container, which could be
the only one running. failed() now keeps a healthy new container, and
errors in the steps after a status change are logged without flipping
a finished deployment to failed.
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.
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.
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.
- 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.
- 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
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
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.
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.
- Stop the application on every server, also when one server is not functional.
- Write file mounts on every server of the application, not only on the main server.
- Deploy the exact commit, rollback flag and image tag of the main deployment to the
additional servers, so a rollback reverts all servers.
- Pull the pushed image on additional servers for inline Dockerfile applications
instead of building it again on each server.
- Fail multi-server deployments without a Docker image name, also from webhooks and the API.
- Send one deployment notification after all servers finish (parent_deployment_uuid).
- Block persistent volumes on multi-server applications (API, Livewire, SQLite connect)
and warn in the deployment log for existing applications that have both.
- Remove dead code in push_to_docker_registry().
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>
Switching an external volume from the old <uuid>_<name> volume to the
declared external volume on redeploy could make an app look as if it
lost its data (for example a database that initializes a new, empty
data directory).
- If a resource already has the storage entry with the old generated
name, the parsers keep using that volume and show a warning in the
deployment log and the service start log, with the steps to switch.
- Without that entry (new resources, or external volumes added later)
the external volume is used as declared.
- Deleting the old storage entry (storage page) switches to the
external volume on the next deployment; the storage entry shows a
note about it.
- Legacy Compose applications (parser versions 1 and 2) never stored
their volumes, so they keep the old volume names.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Compose content files are written by a queued job. Deployments and
service starts now check all content files with one command before
docker compose up: a missing file or an empty directory (left by
Docker) is written; an existing file is kept; a directory with files
is kept with a visible warning.
- The server file sync that runs after every parse marked a file
storage as a directory and deleted its configured content when a
directory was at its path. A storage with content now keeps its
content; an empty directory is replaced with the file.
- Saving a storage no longer fails for an existing file, replaces an
empty directory, and no longer deletes a directory with files.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Deleting a Docker Compose preview removed every top-level network
and volume named in the compose file, so it could disconnect
coolify-proxy from the coolify network or a shared proxy network and
delete a shared named volume. It now removes only the network and
volumes that Coolify generated for that preview, and a network only
when no other container uses it.
- A deployment that stops with an explained error (unsafe compose,
missing secret keys, invalid build variable names) shows the
explanation once, followed by "Deployment failed.".
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Git-based Docker Compose applications skipped the Compose injection
validation that services use. It now runs when the file is loaded or
reloaded, when the raw Compose is saved (UI and API create), and at
deployment before any command uses the file; an unsafe file is not
saved, and a deployment stops with a clear log line.
- All 371 service templates and realistic Compose files still pass.
- Network names may now mix text with $VAR, ${VAR}, ${VAR:-default},
or ${VAR-default} (for example ${COMPOSE_PROJECT_NAME}_default), so
such existing applications keep deploying; command substitution and
unsafe defaults stay rejected.
- Quote the preserved-repository path in a stat command.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
When a Compose service uses a remote Git context, variables, or an
inline Dockerfile, Coolify cannot add ARG lines, and Docker ignores
build-time variables that the Dockerfile does not declare. The log
line now gives the reason and lists the variable names (never values,
at most 10) that need an ARG line in the Dockerfile.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- A failed command marked skip_command_log (for example the .env,
build-time env, and SSH key writes) no longer puts its text into the
exception, which was shown as the visible "Deployment failed" line
with all secrets as base64. Its error output is also cleaned.
- Mark more secret-carrying commands as sensitive: the helper container
with build secrets, railpack prepare, the Nixpacks plan, and Compose
file writes.
- Dev debug lines list only variable names, not values.
- Invalid build-time variable names such as my-var stop a deployment
only when it builds an image. Docker image deployments log a warning
with a suggested name instead.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Runtime-only environment variables whose names new variables can no longer
use (e.g. my-var) no longer fail the deployment. They are still passed to
the container through the .env file, and the deployment log now shows a
warning with a suggested valid name.
Build-time variables, and names that would break a .env line (empty or
containing =, newline, carriage return or NUL), are still rejected.
- Proxy: list and delete Traefik ACME certificates from the server proxy
page via new TraefikAcmeService and Get/DeleteTraefikCertificate actions
- DNS: track ownership and cross-resource references for managed DNS
records so records are only deleted when no longer referenced; release
records asynchronously on resource deletion via ReleaseManagedDnsRecordsJob
and ManagedDnsRecordCleanup; harden Cloudflare provider deletion results
- Databases: fail closed on start when prerequisites or the CA certificate
are missing (DatabaseStartException, Server::ensureCaCertificate) and
clean up stale start activities via ResourceStartActivity
- Webhooks: throttle repeated manual webhook signature failures for
GitHub, GitLab, Gitea and Bitbucket
- Deployments: improve compose build-context handling and compose file
load error reporting
- Install scripts: rework terminal UI output in install.sh (stable and
nightly)
- Misc: settings sidebar accordion fixes, log drain toggle rollback,
add Serverside to README sponsors
- Add migrations and tests covering the above
The server role migration gives every normal server the combined role, and
the build server queries counted combined servers as build servers.
"Use a build server" then built on a random production server, the resource
picker listed each server twice, and "Deployments only" did not stop builds.
- Build server selection and the picker use only "Builds only" servers.
A null role falls back to the legacy is_build_server flag.
- Without a dedicated build server, builds fall back to the deployment
server, never to another combined server.
- A "Deployments only" server always builds on a build server and needs a
Docker image name. It never builds itself, except for restarts. Docker
image and Compose applications are not affected.
- Setting "Deployments only" requires a dedicated build server.
- The API keeps is_build_server in sync with the role for downgrades.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reject names that are not portable identifiers before a deployment starts
and when Docker flags are built. Quote the full KEY=value assignment for
docker run -e flags, and declare generated Dockerfile ARGs as keys only.
Use the shared Docker container-name rules for custom internal names
in the API and Advanced settings form. Quote container names in
deployment inspect, log, and stop commands.
Use the existing git ref validator when a deployment is queued and
when a queued deployment starts, so only well-formed commit values
are stored and used.
Pull image-based Docker Compose services with --ignore-buildable before stopping the current deployment so image-only services are ready first. Fail the deploy if pull fails, and run the pull on the runtime server when a build server is used.