coolify-security — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited coolify-security (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
Covers secrets management, RBAC, network isolation, resource limits, and access control for Coolify v4 self-hosted.
Self-Hosted Responsibility: Coolify self-hosted means YOU own server security. Coolify manages application-layer orchestration, not OS-level hardening.
| Type | Scope | Visibility | Use For |
|---|---|---|---|
| Runtime Env Vars | Running container | Visible via docker inspect | Database URLs, API keys, feature flags |
| Build-time Env Vars | Build process | Visible in build logs + image layers | npm tokens, build-time API keys |
| Docker Build Secrets | Build process only | NOT in image layers or logs | Private registry tokens, signing keys |
START: When is this secret needed?
│
├─ Only at runtime (app needs it while running)?
│ └─► Runtime Environment Variable
│ Set "Build Variable" = OFF in Coolify
│
├─ Only at build time (npm install, Docker build)?
│ ├─ Is it acceptable if this value appears in image layers?
│ │ ├─ Yes → Build-time Environment Variable
│ │ └─ No → Docker Build Secret
│ │ (requires Dockerfile with --mount=type=secret)
│ │
│ └─ None of the above → Docker Build Secret (safest)
│
├─ Needed at both build AND runtime?
│ └─► Two separate entries: one build-time, one runtime
│ Do NOT use build-time alone (baked into image)
│
└─ Shared across multiple apps?
└─► Shared Environment Variable (project-level or team-level)
Coolify supports shared variables that multiple resources referenceFor secrets that must not appear in image layers:
In Coolify: Enable "Use Docker Build Secrets" checkbox on the environment variable. Coolify automatically:
--secret id=KEY,env=KEY instead of --build-arg# syntax=docker/dockerfile:1 to the Dockerfile if missing--mount=type=secret into every RUN instruction (no manual Dockerfile changes needed)COOLIFY_BUILD_SECRETS_HASH to maintain build cache integrityNo Dockerfile modification required — Coolify handles the BuildKit secret injection automatically. Coolify uses BuildKit by default, so this works without additional server configuration.
Manual Dockerfile approach (if you prefer explicit control):
# syntax=docker/dockerfile:1
FROM node:20-alpine AS builder
WORKDIR /app
COPY package*.json ./
RUN --mount=type=secret,id=npm_token \
NPM_TOKEN=$(cat /run/secrets/npm_token) npm ci
COPY . .
RUN npm run buildNew in beta.471 (April 2026): Coolify expanded shared variables to four scopes with explicit interpolation syntax. Earlier versions only supported project-level shared variables.
Four scopes available:
| Scope | Where | Interpolation syntax | Use case |
|---|---|---|---|
| Server | Server settings | {{server.VAR}} | Variables specific to one host (e.g., BACKUP_S3_BUCKET per server) |
| Team | Team settings | {{team.VAR}} | Org-wide values (e.g., MONITORING_API_KEY) |
| Project | Project → Shared Variables | {{project.VAR}} | Project-wide config (e.g., STRIPE_PUBLISHABLE_KEY shared across services) |
| Environment | Project → Environment | {{environment.VAR}} | Per-environment overrides ({{environment.DATABASE_URL}} differs between staging and production) |
Reference example:
DATABASE_URL={{environment.DATABASE_URL}}
STRIPE_KEY={{project.STRIPE_PUBLISHABLE_KEY}}
SLACK_WEBHOOK={{team.SLACK_DEPLOY_WEBHOOK}}Rotation: Update the shared variable and redeploy affected applications. There is no automatic propagation — redeployment is required. Plan rotation around your deployment cadence.
Older Coolify versions: Only project-level shared variables exist; the team/environment/server scopes are unavailable. Verify coolify_version MCP tool returns ≥ beta.471 before relying on them.
| Role | Capabilities | Limitations |
|---|---|---|
| Owner | Full access: create/delete all resources, manage team members, access all settings, view/edit all secrets, terminal access, manage servers and billing | None |
| Admin | Manage apps/databases/projects, delete member-created resources, modify terminal access settings | Cannot delete owner-created resources, cannot manage users or servers |
| Member | View and collaborate on assigned projects, see redacted secrets, delete own deployments | Cannot manage team members, cannot modify server settings, limited secret visibility |
Coolify v4 RBAC operates at the team level:
read-only, read:sensitive, * (full CRUD), or deploy (trigger deploy only)Workaround for per-app isolation: Create separate teams for each project or security boundary. Each team has its own resources and RBAC.
coolify Docker NetworkAll Coolify-managed containers join the coolify Docker network (bridge mode):
To prevent two apps on the same server from communicating:
Reality: Coolify's networking model assumes trust within a server. For true multi-tenant isolation, use separate servers or a container orchestrator with network policies (Kubernetes).
| Method | Access | Use For |
|---|---|---|
| Traefik routing (default) | HTTP/HTTPS only, via domain | Web applications |
| Port mapping | Any TCP/UDP port on host | Databases, custom protocols |
| No exposure | Internal only (coolify network) | Background workers, internal services |
Security rule: Only expose ports that must be accessible externally. Default to internal-only.
Docker modifies iptables directly, bypassing UFW rules. This means:
deny rules do NOT block Docker-published ports# Option 1: Disable Docker's iptables manipulation (affects ALL containers)
# /etc/docker/daemon.json
{
"iptables": false
}
# Then restart Docker: systemctl restart docker
# WARNING: This breaks inter-container networking; requires manual iptables rules
# Option 2 (Recommended): Use ufw-docker utility
# https://github.com/chaifeng/ufw-docker
wget -O /usr/local/bin/ufw-docker https://github.com/chaifeng/ufw-docker/raw/master/ufw-docker
chmod +x /usr/local/bin/ufw-docker
ufw-docker install
# Allow specific access
ufw-docker allow <CONTAINER_NAME> 5432/tcp
ufw-docker allow <CONTAINER_NAME> 5432/tcp from 203.0.113.50127.0.0.1 only: 127.0.0.1:5432:5432 (not 0.0.0.0:5432:5432)ufw-docker to make UFW rules apply to Docker containersIn application or database settings:
| Setting | Purpose | Default |
|---|---|---|
| Memory Limit | Maximum memory the container can use | Unlimited (dangerous) |
| Memory Reservation | Soft limit / guaranteed minimum | Not set |
| CPU Limit | Maximum CPU cores (e.g., 1.5) | Unlimited |
| CPU Reservation | Guaranteed CPU minimum | Not set |
When a container hits its memory limit:
unless-stopped) restarts the containerdocker inspect <CONTAINER> | grep -i oomBuild processes do NOT have separate resource limits in Coolify. A runaway build can consume all server resources. Mitigation:
NODE_OPTIONS=--max-old-space-size=2048 for Node.js buildsCoolify provides a web terminal in the UI for running commands inside containers:
Current state: Coolify v4 has limited audit logging:
Workaround: Enable server-level auditd for SSH and Docker command logging.
| Anti-Pattern | Consequence |
|---|---|
| Storing secrets as build-time env vars when only needed at runtime | Secrets baked into Docker image layers, extractable via docker history |
| Using the same API token for all CI/CD pipelines | Compromising one pipeline compromises everything |
| Not setting memory limits on any container | One runaway process can OOM-kill the entire server |
| Relying on UFW alone to protect Docker-published ports | UFW rules are bypassed by Docker's iptables manipulation |
| Giving Developer role to users who should be Viewers | Developers can access terminal, modify env vars, trigger deployments |
Running containers as root without USER directive | Container escape gives root on host if Docker is not hardened |
Using --privileged flag on any container | Full host access; defeats all container isolation |
| Exposing database ports to 0.0.0.0 | Database accessible from any IP; bots will find it |
| Not rotating API tokens or database credentials | Long-lived credentials increase blast radius of compromise |
| Sharing one team for all projects | No isolation between projects; one compromised member affects everything |
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.