Docker, CI/CD, and Coolify: an automated release factory

Docker, CI/CD, and Coolify: an automated release factory

Updated: 14 min read
  • Docker
  • CI/CD
  • Coolify
  • DevOps
  • Laravel
  • WordPress

Picture a factory where every product leaves with the same mold, the same label, and a quality check before it reaches the customer. That is a solid Docker + CI/CD + Coolify pipeline: not “upload whatever worked on my laptop,” but a repeatable process that turns code into a trustworthy release.

This article explains, in plain language with concrete steps, how to deploy Laravel APIs, Node apps, and containerized WordPress sites. It helps product owners who want fewer incidents, and technical teams that need a real runbook — not just a loose Dockerfile.

Why it matters for the business

Every improvised deploy is risk: a payment that stops charging, a form that dies on a Friday, or an impossible rollback because nobody remembers which version is running. A good pipeline shortens time-to-ship, makes staging believable, and turns deploy into a measurable operation — not a midnight ritual.

  • Less “works on my machine”: the same artifact is tested and promoted.
  • Predictable rollback: you return to the previous tag in minutes, not by guessing.
  • Audit trail: you know which commit is in production and who promoted it.
  • Team scale: a junior can deploy without breaking the senior’s ritual.

Key concepts (simple + technical)

Docker: the sealed box

Docker packages the app with its dependencies into an image. Think of a shipping container: the dock (server) may change, the content travels the same way. Technically: layers, a multi-stage Dockerfile, and a runtime without build toolchains or secrets.

CI/CD: the assembly line

CI (continuous integration) validates every change: lint, tests, build. CD (continuous delivery/deployment) moves the artifact to staging and, with quality gates, to production. Coolify shines at deploy (SSL, variables, restarts); CI still lives in GitHub Actions, GitLab CI, or another runner.

Immutable tags vs latest

Tagging only as latest is like labeling a box “current version” with no lot number. Use registry/app:gitsha: you know exactly what runs and can redeploy that digest.

PieceAnalogyWhat it does in practice
Docker imagePackaged productSame binary/artifact in staging and prod
CI pipelineQuality controlLint, tests, scan, build
CoolifyWarehouse + shippingDeploy, SSL, env vars, healthcheck
SHA tagLot numberExact rollback and traceability

Practical guide: minimum pipeline

  1. PR: lint + typecheck + unit tests (and critical e2e if there is checkout).
  2. Multi-stage build: deps → build → small runtime.
  3. Immutable tag: registry/app:gitsha (never only latest).
  4. Basic image scan (obvious CVEs, root user, secrets in layers).
  5. Deploy to staging on Coolify with the same variables (different values).
  6. Smoke / healthcheck: cheap route that verifies app + DB.
  7. Promote to production with the same digest/tag — no different rebuild.

Multi-stage for Laravel / Node

  • deps: Composer/npm with ordered layer caching.
  • build: npm ci + npm run build, php artisan optimize, assets.
  • runtime: non-root user, no toolchain, no baked-in .env.
  • Correct permissions on storage and bootstrap/cache (Laravel).

WordPress in Docker

  • Persistent volume for wp-content/uploads.
  • Secrets via env / runtime-generated wp-config — never in the image.
  • Object cache (Redis) as a separate service when traffic demands it.
  • Multisite: domains, cookies, and paths documented.
  • DB + uploads backup before major updates.

Coolify configuration

  • Variables and secrets outside the Dockerfile.
  • Real healthcheck (HTTP 200 on a stable route, not just “the process exists”).
  • Canonical apex domain; www with a 301 redirect.
  • Let's Encrypt SSL on both hosts.
  • Staging and production separated; restart policies and accessible logs.

Common mistakes

  • Different rebuild in prod: you no longer know what runs or how to reproduce the bug.
  • Healthcheck hitting / with a 301 redirect → unhealthy loop.
  • WordPress volumes owned by root → broken uploads.
  • Running migrate --force twice in parallel.
  • Forgetting the queue worker: the web responds, webhooks never process.
  • Secrets in Git or Docker layers “for convenience.”

Pre-production checklist

  • Tag = git SHA (not only latest).
  • Secrets absent from the repo and Docker layers.
  • Green healthcheck on staging with the same image.
  • Migrations rehearsed + recent backup.
  • Apex / www and SSL verified.
  • Workers and cron alive after deploy.
  • Written rollback plan (redeploy previous SHA).
  • Staging and prod variable names aligned, values different.

A poorly deployed checkout is a time bomb. The pipeline connects to payments, and security auditing .

Frequently asked questions

Does Coolify replace a CI pipeline?

Not entirely. Coolify shines at deploy, SSL, variables, and restarts. CI (lint, tests, build) still belongs in GitHub Actions, GitLab CI, or another runner. The usual pattern is to combine them: CI produces the artifact; Coolify runs it.

Can WordPress run in Docker?

Yes, carefully: volumes for uploads, correct permissions, and never leave secrets in the image. For very simple sites it may not be worth it; for teams already living in containers, yes.

Why avoid the latest tag in production?

Because `latest` moves. A git SHA tag enables exact rollback and answers “which commit is in prod?” without archaeology.

How should database migrations be handled?

As an explicit, controlled release step, with a recent backup and a staging rehearsal. Not as “someone runs artisan by hand in production” with no record.

What should the healthcheck include?

A cheap route that confirms the app responds and, if applicable, that the DB is reachable. Avoid hitting a home page with redirects or a page that depends on a slow CDN.