Skip to content

[Bug] HEALTHCHECK detection ignores dockerfile_target_build in multi-stage Dockerfiles #9475

@jeongkichang

Description

@jeongkichang

Description

When using a multi-stage Dockerfile with dockerfile_target_build set to a specific stage, Coolify's HEALTHCHECK detection parses the entire Dockerfile text instead of only the target stage. This causes incorrect healthcheck configuration to be applied.

Steps to Reproduce

  1. Create a multi-stage Dockerfile with two targets:
# === Stage: builder ===
FROM node:20-alpine AS builder
# ... build steps ...

# === Stage: api ===
FROM node:20-alpine AS api
COPY --from=builder /app/dist ./dist
HEALTHCHECK --interval=30s --timeout=5s --start-period=15s --retries=3 \
    CMD node -e "require('http').get('http://localhost:3000/health', (r) => { process.exit(r.statusCode === 200 ? 0 : 1) })"
CMD ["node", "dist/main.js"]

# === Stage: worker ===
FROM node:20-alpine AS worker
COPY --from=builder /app/dist ./dist
# No HEALTHCHECK — this stage has no HTTP server
CMD ["node", "dist/worker.js"]
  1. Create two Coolify applications from the same repo:

    • API app: dockerfile_target_build = api
    • Worker app: dockerfile_target_build = worker, Health Check disabled in UI
  2. Deploy the Worker app.

Expected Behavior

  • Worker app should have no healthcheck, because the worker stage has no HEALTHCHECK instruction.
  • Coolify should only parse HEALTHCHECK from the stage specified in dockerfile_target_build.

Actual Behavior

  • Coolify detects HEALTHCHECK from the api stage and sets custom_healthcheck_found = true on the Worker app.
  • The Worker container gets the API's healthcheck configuration applied, which fails because there's no HTTP server on port 3000.
  • Adding HEALTHCHECK NONE to the worker stage doesn't help — Coolify still detects the string "HEALTHCHECK" and parses the first occurrence (from the api stage).

Root Cause

In app/Models/Application.php, the parseHealthcheckFromDockerfile method:

// Line ~2146 (latest main branch, confirmed on v4.0.0-beta.470)
$hasHealthcheck = str($dockerfile)->contains('HEALTHCHECK');

This checks the entire Dockerfile content for the substring "HEALTHCHECK" without considering which stage is the build target. The subsequent foreach loop also processes the first HEALTHCHECK instruction found, regardless of which stage it belongs to.

The dockerfile_target_build value (used in ApplicationDeploymentJob.php for docker build --target) is not passed to or considered by parseHealthcheckFromDockerfile.

Suggested Fix

parseHealthcheckFromDockerfile should be stage-aware when dockerfile_target_build is set:

  1. Parse FROM ... AS <stage_name> lines to identify stage boundaries
  2. Only search for HEALTHCHECK within the target stage's line range
  3. Handle HEALTHCHECK NONE as an explicit disable (currently not handled)

Workaround

Separate the Dockerfile into two files (e.g., Dockerfile.api and Dockerfile.worker) so each Coolify application only sees its own HEALTHCHECK configuration.

Environment

  • Coolify version: v4.0.0-beta.470 (also confirmed on earlier v4.x)
  • Build pack: Dockerfile
  • Multi-stage: Yes, with dockerfile_target_build

Additional Context

This issue affects any multi-stage Dockerfile where different stages have different healthcheck requirements (e.g., API server + background worker from the same codebase).

Metadata

Metadata

Assignees

No one assigned

    Labels

    🐛 Possible BugReported issues that need to be reproduced by the team.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions