Lesson  in  ColdFusion 2025: Foundations

CI/CD for CFML Applications

Build automated pipelines for ColdFusion apps using CommandBox, GitHub Actions, and Docker. Package, test, and deploy CFML apps without touching a server manually.

What is CI/CD?

CI (Continuous Integration) means every code change is automatically tested before it is merged. CD (Continuous Delivery) means every passing build is automatically packaged and ready to deploy.

For a ColdFusion application the full pipeline looks like this:

Linear pipeline diagram showing five stages connected by rightward arrows — stage 1 "Git push", stage 2 "CI server triggered", stage 3 "box install + box testbox run" with a red X showing failing tests stop the pipeline, stage 4 "docker build -t cfml-app", stage 5 "push to registry and deploy"

Tests gate the build — a failing TestBox run stops the Docker image from being created.

Note

📌 Scope of this lesson

Running a complete CI/CD pipeline requires a Git server, a container registry, and a CI runner. That infrastructure is out of scope for this foundations course.

In this lesson you will build the two artefacts the pipeline depends on: a box.json manifest and a Dockerfile. Then you will build a Docker image locally — the same command the pipeline runs.

The full pipeline — Git server, registry, automated tests, and deployments using Gitea — is covered in the ColdFusion Advanced course.


1. box.json — the project manifest

box.json is the CommandBox project manifest. It declares your app name, version, and ForgeBox dependencies — similar to package.json in Node.js or composer.json in PHP.

{
  "name": "helpdesk-app",
  "version": "1.0.0",
  "dependencies": {
    "testbox": "^5.0.0"
  }
}

box install reads this file and installs all declared packages into a modules/ directory. In a CI pipeline the build agent runs box install first — this file is the only thing it needs to recreate the full dependency tree on a clean machine.


2. Dockerfile — containerise your app

Annotated Dockerfile with four callout labels — FROM ortussolutions/commandbox:latest labelled "Official CommandBox base image (Java + Lucee bundled)"; COPY and WORKDIR labelled "Copy project files"; RUN box install --production labelled "Install dependencies, skip dev packages"; EXPOSE 8888 and CMD labelled "Start server in foreground"

The base image handles the runtime — you just copy code and install packages.

FROM ortussolutions/commandbox:latest

COPY . /app
WORKDIR /app

RUN box install --production

EXPOSE 8888
CMD ["box", "server", "start", "--console"]
LineWhat it does
FROM ortussolutions/commandbox:latestOfficial CommandBox base — Java + Lucee already bundled
COPY . /appCopies your CFML project files into the image
RUN box install --productionInstalls ForgeBox dependencies, skips dev packages like TestBox
EXPOSE 8888Documents which port the server listens on
CMD [...]Starts the Lucee server in the foreground when the container runs
💡 Why not bake credentials into the Dockerfile?

A Docker image is a portable artefact — anyone who can pull it can inspect every layer. Credentials baked into the image (datasource passwords, API keys) are exposed to anyone with registry access, and they end up in git history too.

The right pattern is to inject credentials at runtime via environment variables. In a CI pipeline, secrets are stored in the CI server (GitHub Actions Secrets, Gitea Secrets) and passed to the container on start — never written into the image.


Activity 1 — Create box.json

Create /home/laborant/app/box.json:

mkdir -p /home/laborant/app
tee /home/laborant/app/box.json << 'EOF'
{
  "name": "helpdesk-app",
  "version": "1.0.0",
  "dependencies": {
    "testbox": "^5.0.0"
  }
}
EOF

Verify:

cat /home/laborant/app/box.json

Activity 2 — Create the Dockerfile

Create /home/laborant/app/Dockerfile:

tee /home/laborant/app/Dockerfile << 'EOF'
FROM ortussolutions/commandbox:latest

COPY . /app
WORKDIR /app

RUN box install --production

EXPOSE 8888
CMD ["box", "server", "start", "--console"]
EOF

Verify:

cat /home/laborant/app/Dockerfile

Activity 3 — Build the Docker image

Build the image tagged cfml-app:

docker build -t cfml-app /home/laborant/app/

Confirm it exists:

docker images | grep cfml
🐢 First build is slow — here is why.

Docker pulls the ortussolutions/commandbox:latest base image on the first build — roughly 500 MB. Subsequent builds reuse cached layers and complete in seconds. The RUN box install --production step is also cached after the first run as long as box.json has not changed.


When all the checks above are green, this lesson is complete. Your progress is saved automatically — move straight on to the next lesson.

Note

Found a bug or an issue with this lesson? Please reach out — your feedback helps improve the course for everyone.

📧 Alex — mercadoalexatgmail.com