Use the helper's RAILPACK_VERSION, falling back to the installed CLI
version, instead of a hardcoded frontend version. Add regression tests
for both version sources and application-variable isolation.
- Stop the Redis username accessor from creating REDIS_USERNAME on read
- Keep source values when cloning applications (no duplicate NIXPACKS_NODE_VERSION)
- Match exact keys when adding default database environment variables
- Write content files before database containers start
- Resolve the database proxy upstream through Docker DNS on each connection
- Remove existing duplicate REDIS_USERNAME and NIXPACKS_NODE_VERSION rows
Store server IPs in one form (no brackets, short lower-case IPv6) and
write each address in the form its context needs:
- URLs and host:port (Sentinel, database URLs, SFTP backup download,
DNS servers, SMTP, webhook base URLs, links, health checks) get
brackets through formatHostForUrl().
- Traefik Host() rules use the address without brackets, so IPv6
domains no longer return 404; saved labels are fixed at deploy.
Caddy keeps the brackets.
- SSH keeps the plain address.
- Hetzner gives an IPv6 /64 network; IPv6-only servers now get the
server address (prefix::1) instead of the unusable prefix::64.
- sslip labels, registry image names with an IPv6 host, the API domain
list, allowlist matching and duplicate checks handle IPv6.
A migration normalizes stored IPv6 server addresses, repairs Hetzner
servers saved as ::64 that are not reachable, and adds brackets to
saved Sentinel URLs.
Put the user menu above the toggle in the collapsed sidebar, make the
user menu a full-width 3D button with rounded corners, and use a new
sidebar toggle icon everywhere.
Docker publishes the proxy port on [::] too and forwards it to the
container's IPv6 address when the network has IPv6 enabled. nginx
listened only on IPv4, so every IPv6 connection was reset.
Add listen [::]:<port> when the destination network has IPv6 enabled.
Networks without IPv6 keep the IPv4-only listener.
Fixes#12133
An upgrade pulls new images and recreates the Coolify containers. A full
disk during an upgrade can leave Coolify broken.
- Measure free space on the Docker root dir and /data/coolify of localhost;
require 5 GB. An unknown value does not block.
- Manual upgrade: show the free and required space, with Run Docker cleanup,
Check again, and a confirmed Upgrade anyway override.
- Automatic upgrade: skip it and notify the team (disk usage channels, at
most once per 24 hours per server).
- upgrade.sh: stop before any change when the disk is low. A new 5th
argument skips the check (used by the override).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Laravel writes bootstrap/cache/config.php in place, so the file is empty
until the write ends. app:init rebuilds the cache on every container start
while PHP-FPM already serves requests. A request could read the empty file
(TypeError in LoadConfiguration), and OPcache with validate_timestamps=0
kept it, so every request failed until a restart.
Write to a temporary file in the same directory and rename it over the
target. A failed write now keeps the old file instead of an empty one.
Fixes#12120
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Accept HTTP(S), git:// and SCP-style SSH repository URLs whose host is an
IP address, including bracketed IPv6 such as git@[2a01:4f8::1]:org/repo.git.
The clone runs on the user's deployment server, so the IP block did not
protect Coolify, and SSH URLs with IPv4 already passed.
Loopback, link-local and other reserved addresses are still rejected as
internal hosts. Brackets are allowed only around a valid IPv6 host.
Resolve application owners from dash-separated UUIDs in Compose service or coolify.name labels, and include service-prefixed names in Docker filters.
Add regression coverage for custom Compose start commands, status updates, and UUID boundary matching.
Accept the `?`, `:?`, `+` and `:+` interpolation operators in Compose
network names, such as `${CONTAINER_NAME?error}`. The validation added
in v4.4.0 only knew `-` and `:-`, so these valid files stopped deploying.
The text after `?` is an error message and is not used as the default of
the created environment variable. Unsafe operands are still rejected.
Fixes#12126
Older Infisical versions default expandSecretReferences to false, so
values like prefix-${OTHER_KEY} reached containers unexpanded. Request
expanded values explicitly on the v4 endpoint and the v3 fallback.
Fixes#12128
mc --resolve applies per invocation and is not stored in the alias, so
mc cp resolved the S3 host again inside the helper container. On hosts
with split DNS this failed with "no such host", and the upload did not
use the IP validated by SafeWebhookUrl.
Fixes#12129
Hide the empty description line in the environment resources table so
it takes no space. Names without a description now sit level with the
other cells, and on narrow screens the domain follows the name directly.
Fixes#12127
The install callback swallowed every verification error (clock skew, network,
missing key, GitHub API errors) and returned an opaque 403. It also compared
the state's team with the session team, so switching teams during the GitHub
install returned 403.
- Return the verification reason, log it, and redirect to the GitHub App page
with an error toast (JSON requests still get 403 with the reason)
- Check membership of the state's team, and switch the session to it
- Show flashed errors on the GitHub App page
Fixes#12122
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Block new upgrades while a recent upgrade is running, allowing retries after 15 minutes without a status update.
Add request timeouts and polling guards to keep upgrade progress responsive during restarts.
The Domains page polls every 2 seconds while a DNS check runs. Each poll
parsed the compose file, and the parser queued one ServerFilesFromServerJob
per volume. The jobs starved the DNS check jobs and deployments.
- Skip the compose parse in pollDnsChecks and keep the known services.
- Make ServerFilesFromServerJob unique per resource until it runs.
Fixes#12098
Malformed JSON in the coolify-log-filters localStorage key threw during
runtime log viewer startup. Read the saved value safely and keep only
boolean values for known levels.
Fixes#12101
Caddy logs a site only when its container labels contain the log
directives. Deployments reuse the stored custom_labels, so applications
whose labels were stored before analytics was enabled never got the log
directives, and a redeploy did not help.
Toggling traffic analytics on a Caddy server now regenerates the stored
labels of applications with read-only labels. The destination uses the
current server instance, because the cached server can still have the
old analytics setting.
Fixes#12117
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The commit message was read only during the repository clone. Deployments
that skipped the build or failed before the clone showed only the commit
hash. They now reuse the message stored by an earlier deployment of the
same application and commit.
Closes#12116
Only render the card description when it has text, and center the title
against the icon. Removing the description placeholders left an empty
description element in items-start rows, so titles without a description
were pinned to the top of the icon.
Closes#12102
Pull request deployments sent every non-railpack build pack to build_image(),
which no longer handles the static build pack since it was moved to
build_static_image(). Static preview deployments then failed with
"open Dockerfile: no such file or directory".
Closes#8238
Build release/vX.Y pushes for amd64 and aarch64, then publish
X.Y-dev.<short-sha> and moving X.Y-dev tags to GHCR and Docker Hub.
Report build status to Discord and document the release branch tags.
Update feature PR targets and RC, stable, and hotfix flows to use
release/v4.x branches. Document branch rotation after stable minor
releases while retaining the next Docker image tag.
The container picker read its options from an Alpine x-data snapshot taken
on the first render, before wire:init loaded the containers. Livewire keeps
the Alpine state when it morphs, so resources with multiple containers
showed an empty picker. Publish the options as a Livewire property and
entangle them in Alpine.
Closes#12110
Replace datalist and searchable-listbox components with the shared listbox and migrate form fields.
Deduplicate superseded Livewire updates to prevent repeated array update hooks, with regression coverage.
Add an authorized update button with activity logs to pull the latest image and recreate the tunnel container in the background.
Persist new Compose files under /data/coolify/cloudflared and support existing /tmp configurations. Cover updates, missing configuration, and member restrictions with tests.
Combine Sentinel overflow rows into one unlinked "Other paths" row.
Show domains beside paths and include them in Livewire row keys.
Add regression coverage for overflow aggregation across app keys.
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
Allow full, anonymized, or disabled IP recording per server through the UI
and API, and pass the selected mode to Sentinel. Adapt traffic breakdowns
to the effective IP mode, including views that aggregate multiple servers.