Migration - Upgrades

How to Move ColdFusion Apps to Docker Containers

Contents show

Why migrate ColdFusion applications to containers

Containerizing ColdFusion modernizes delivery, reduces drift between environments, and makes Scaling and recovery far more predictable. With Docker, you can encode server Configuration, ColdFusion packages, and dependencies directly in a Dockerfile. That means dependable builds, faster Onboarding, simplified CI/CD pipelines, and a clearer path to orchestrators like Kubernetes. You also gain isolation, immutability, repeatable releases, and improved Security posture when configured correctly.


Prerequisites / Before You Start

  • Architecture and inventory
    • List every ColdFusion app, versions (Adobe ColdFusion 2016/2018/2021/2023 or Lucee 5.x), Java/JDK level, and OS platforms.
    • Identify application entry points, Scheduled tasks, Web services, WebSocket usage, Solr/Elasticsearch, PDF generation, and report services.
    • Inventory external dependencies: file shares, SMTP, LDAP/AD, S3 buckets, Redis, Message queues (ActiveMQ/RabbitMQ), Payment gateways, license requirements.
  • Backups and exports
    • Full backup of code, libraries, CFAdmin Configuration (datasources, mail servers, mappings, JVM args).
    • For Adobe ColdFusion: export Admin settings using cfsetup (if available) or CFAdmin API scripting.
    • For Lucee: export server.json/web.json (server/web contexts) or use lucee-config tools.
    • Database backups and schema Migration scripts.
    • SSL/TLS certs and keystores.
  • Version targeting and base images
    • Decide on Adobe ColdFusion vs Lucee and choose official base images and tags.
    • Select an OS base (e.g., Debian/Ubuntu vs Alpine; Alpine may need extra packages for fonts/PDF).
    • Confirm Docker Engine and Compose versions on your environment (Docker Desktop or server).
  • Security, secrets, and Licensing
    • For Adobe ColdFusion, plan for serial numbers or EULA acceptance; consider license monitoring in containers.
    • Prepare secrets: DB passwords, mail credentials, API keys. Use Docker secrets or an external secret manager.
  • Externalizing state
    • Decide where to store sessions (e.g., Redis) and user uploads (e.g., S3 or shared volume).
    • Plan persistent volumes for necessary data; keep containers stateless whenever possible.
  • Networking and DNS
    • Decide ports, domain names, Reverse proxy (Nginx/Traefik/Apache), and TLS termination approach.
  • Testing and rollout strategy
    • Environments: local dev, test/staging, production.
    • Rollout: blue/green or canary; define rollback procedures.
  • Monitoring and logging
    • Choose monitoring (Prometheus/Grafana, ELK/EFK, New Relic, Datadog) and log shipping strategy.
See also  How to Handle Character Encoding Issues During Migration

Step-by-step Migration guide

1) Assess, baseline, and fix what’s brittle

  • Run the app locally and in your current environments; note JVM args, system properties, Scheduled tasks, datasources, and CF mappings.
  • Identify OS-specific paths and case sensitivity issues (Linux containers are case-sensitive).
  • Replace absolute file paths with environment-based configurables. Externalize file I/O to volumes or object storage.

2) Choose your container base and image strategy

  • Adobe ColdFusion (commercial): use adobecoldfusion/coldfusion with a specific tag (e.g., 2023.x). Benefits: commercial support, CFSetup, CFPM package manager. Requirements: accept EULA/license.
  • Lucee (open-source): use lucee/lucee (Tomcat) or lucee/lucee-nginx. Benefits: lightweight, flexible; configurations in server.json/web.json.
  • Decide on a single multi-stage Dockerfile per app to build artifacts and assemble the runtime image.

3) Export and codify Server configuration

  • Adobe ColdFusion:
    • Use cfsetup to export Admin settings from your current server to a JSON file.
    • Use CFPM (Package Manager) to list and then install needed packages in the container (e.g., spreadsheet, database drivers).
  • Lucee:
    • Export Server configuration to server.json/web.json. Check for datasources, mail settings, mappings, caching, and security.
  • If export tools aren’t available, script the configuration using CFAdmin API or Application.cfc initialization logic.

4) Build your Dockerfile for Adobe ColdFusion (example)

Dockerfile (Adobe ColdFusion example)

FROM adobecoldfusion/coldfusion:2023

Accept EULA and set the CF Admin password via environment

ENV acceptEULA=YES \
password=${CF_ADMIN_PASSWORD:-SuperSecurePass!} \
enableSecureProfile=true

Install OS-level deps (fonts, wkhtmltopdf alternatives, etc.)

RUN apt-get update && apt-get install -y \
fontconfig locales curl unzip ca-certificates \
&& rm -rf /var/lib/apt/lists/*

Install CF packages (adjust as needed)

The cfpm script is typically in the CF installation bin directory

RUN /opt/coldfusion/cfusion/bin/cfpm.sh install \
mysql mssql postgresql spreadsheet pdf

Copy your application code

WORKDIR /app
COPY ./src/ /app/

Copy configuration exports (cfsetup JSON) and import at build or entrypoint

COPY ./cf-config/cfsetup.json /opt/cf-config/cfsetup.json

Optional: import now; or do it on container start

RUN /opt/coldfusion/cfusion/bin/cfsetup.sh import /opt/cf-config/cfsetup.json || true

Expose internal CF/Tomcat port

EXPOSE 8500

Healthcheck: adjust path to an app health endpoint

HEALTHCHECK –interval=30s –timeout=5s –retries=5 CMD curl -f http://localhost:8500/health.cfm || exit 1

Notes:

  • The ENV variables such as acceptEULA and password are required for first-run setup. Use Docker secrets or env files to avoid hardcoding.
  • Consider importing cfsetup on startup instead of build, if you need environment-specific values.

5) Build your Dockerfile for Lucee (example)

Dockerfile (Lucee + Tomcat example)

FROM lucee/lucee:5.4-tomcat9-jdk11

Install OS-level deps and fonts for PDF rendering, etc.

RUN apt-get update && apt-get install -y \
fontconfig locales ca-certificates curl unzip \
&& rm -rf /var/lib/apt/lists/*

Set the admin password (server/web context)

ENV LUCEE_ADMIN_PASSWORD=${LUCEE_ADMIN_PASSWORD:-SuperSecurePass!}

Copy webroot

WORKDIR /var/www
COPY ./src/ /var/www/

Copy Lucee server/web config

server.json controls server-wide settings (datasources, mail, caches)

COPY ./lucee-config/server.json /opt/lucee/server/lucee-server/context/server.json

Optional: web context config

COPY ./lucee-config/web.json /var/www/WEB-INF/lucee/web.json

EXPOSE 8888
HEALTHCHECK –interval=30s –timeout=5s –retries=5 CMD curl -f http://localhost:8888/health.cfm || exit 1

Notes:

  • For Lucee “nginx” variants, the webroot path differs (often /var/www). Confirm in the official image docs.
  • If you rely on specific extensions, use the Lucee Extension Provider configuration or preinstall them during build.

6) Compose a local stack (Reverse proxy, DB, cache)

docker-compose.yml

version: “3.9”

services:
reverse-proxy:
image: nginx:1.25
ports:

  • “80:80”
    volumes:
  • ./nginx/conf.d:/etc/nginx/conf.d:ro
    depends_on:
  • cfapp

cfapp:
build: .
environment:

  • CF_ADMIN_PASSWORD=${CF_ADMIN_PASSWORD}
  • DB_HOST=db
  • DB_USER=${DB_USER}
  • DB_PASSWORD_FILE=/run/secrets/db_password
  • REDIS_HOST=redis
  • TZ=UTC
    secrets:
  • db_password
    volumes:
  • app_uploads:/data/uploads
    expose:
  • “8500”
    healthcheck:
    test: [“CMD”, “curl”, “-f”, “http://localhost:8500/health.cfm“]
    interval: 30s
    timeout: 5s
    retries: 5

db:
image: mysql:8.0
environment:

  • MYSQL_DATABASE=appdb
  • MYSQL_USER=${DB_USER}
  • MYSQL_PASSWORD_FILE=/run/secrets/db_password
  • MYSQL_ROOT_PASSWORD_FILE=/run/secrets/db_root_password
    volumes:
  • dbdata:/var/lib/mysql
    secrets:
  • db_password
  • db_root_password

redis:
image: redis:7

secrets:
db_password:
file: ./secrets/db_password.txt
db_root_password:
file: ./secrets/db_root_password.txt

volumes:
dbdata:
app_uploads:

  • Nginx proxies to the ColdFusion container via upstream http://cfapp:8500.
  • Use Docker secrets for sensitive values.
  • Externalize uploads to a persistent volume or S3.

7) Configure web server Integration

  • Avoid AJP unless you have a strict need; HTTP proxy is simpler and safer.
  • Nginx sample location block:

nginx/conf.d/app.conf

server {
listen 80;
servername ;

location / {
proxy_pass http://cfapp:8500;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Forwarded-For $remote_addr;
}

Static assets could be served directly if mounted

location /assets/ {
root /usr/share/nginx/html;
}
}

8) Datasources, mail, caches, and scheduled tasks

  • Datasources:

    • Adobe: define via cfsetup or CFAdmin API; ensure JDBC drivers are installed via CFPM.

    • Lucee: define in server.json. Example snippet:

      {
      “datasources”: {
      “appdb”: {
      “class”: “com.mysql.cj.jdbc.Driver”,
      “connectionString”: “jdbc:mysql://db:3306/appdb?useSSL=false&allowPublicKeyRetrieval=true”,
      “username”: “${env:DB_USER}”,
      “password”: “${env:DB_PASSWORD}”
      }
      }
      }

  • Mail server:

    • Configure host/port/credentials; test via a mail sandbox or dev SMTP service.
  • Sessions:

    • Move to Redis for cluster-friendly Scaling. In Adobe, enable J2EE sessions or Redis session manager; in Lucee, use sessionStorage=redis via extension/config.
  • Scheduled tasks:

    • Export and reimport tasks (cfsetup for Adobe or Admin API for Lucee). Consider cron outside CF if tasks are HTTP-based, or keep within CF if they rely on server state.
See also  How to Handle Deprecated Tags During ColdFusion Upgrade

9) JVM and CF server tuning in containers

  • JVM heap sizing via environment (e.g., JAVA_TOOL_OPTIONS or setenv scripts). Example:
    • -Xms512m -Xmx2048m -XX:MaxMetaspaceSize=512m
  • Time zone: set TZ and ensure the image has tzdata installed if needed.
  • Security profile: enable secure profile and disable RDS, Debugging, and unused endpoints.
  • Harden HTTP headers at the reverse proxy (HSTS, X-Frame-Options, CSP as appropriate).

10) Logging and observability

  • Emit app logs to stdout/stderr; avoid writing logs inside the container filesystem.
  • Configure access logs at Nginx; ingest via a log forwarder.
  • Add a simple /health.cfm that checks DB connectivity and critical dependencies.
  • Export Prometheus metrics if available, or integrate JMX metrics via an agent.

11) Build, run, and iterate locally

12) CI/CD pipeline

  • Use a pipeline to:
    • Lint Dockerfile, build image, run unit/Integration tests.
    • Scan image for vulnerabilities (Trivy, Grype).
    • Tag and push to a private registry.
    • Deploy to staging, run smoke tests, then promote to production via blue/green.

13) Cutover plan

  • Prepare blue/green stacks behind the reverse proxy.
  • Warm up CF caches and compile templates by calling key pages.
  • Switch traffic via DNS or load balancer target groups.
  • Monitor error rates, latency, memory/CPU, and rollback quickly if needed.

14) Scale and cluster

  • Horizontal scaling:
    • Make the app stateless (sessions in Redis, files in S3 or shared volume).
    • Disable in-memory caches that aren’t cluster-aware or switch to distributed caches.
  • For Kubernetes:
    • Translate compose to manifests or use Helm.
    • Add readiness/liveness probes and resource requests/limits.

Configuration examples you can adapt

Adobe ColdFusion CFPM install in entrypoint

!/bin/bash

set -e

if [ -n “$CFPM_PACKAGES” ]; then
IFS=’,’ read -ra pkgs <<< “$CFPM_PACKAGES”
for p in “${pkgs[@]}”; do
/opt/coldfusion/cfusion/bin/cfpm.sh install “$p”
done
fi

Optional: import admin config if a path is provided

if [ -f “/opt/cf-config/cfsetup.json” ]; then
/opt/coldfusion/cfusion/bin/cfsetup.sh import /opt/cf-config/cfsetup.json || true
fi

exec “$@”

Then set CFPM_PACKAGES=spreadsheet,mysql,mssql in the environment.

Lucee server.json with environment interpolation

{
“allowRequestsFromLoopback”: true,
“datasources”: {
“appdb”: {
“class”: “org.postgresql.Driver”,
“connectionString”: “jdbc:postgresql://${env:DB_HOST}:5432/${env:DB_NAME}”,
“username”: “${env:DB_USER}”,
“password”: “${env:DB_PASSWORD}”
}
},
“mail”: {
“mailservers”: [{
“host”: “${env:SMTP_HOST}”,
“port”: 587,
“username”: “${env:SMTP_USER}”,
“password”: “${env:SMTP_PASSWORD}”,
“tls”: true
}]
}
}


Risks, Common Issues, and How to Avoid Them

  • Licensing and EULA violations
    • Risk: Running Adobe ColdFusion without proper licensing or not setting acceptEULA.
    • Avoid: Set acceptEULA=YES and configure license keys; consult Adobe licensing for containerized deployments.
  • Case sensitivity and file paths
    • Risk: Moving from Windows to Linux breaks includes or mappings due to case.
    • Avoid: Normalize case in code and verify cfinclude/template paths.
  • Native dependencies and fonts
    • Risk: PDF generation or cfdocument fails without fonts/headless browser libs.
    • Avoid: Install fontconfig, common fonts, and verify chromedp/wkhtml-like dependencies if used.
  • JDBC drivers and packages
    • Risk: Missing database drivers in Adobe CFPM or Lucee extensions.
    • Avoid: Install packages/extensions at build; test connection pooling under load.
  • Session state loss on scale-out
    • Risk: Sticky sessions or in-memory sessions cause user logouts during scaling.
    • Avoid: Externalize sessions to Redis; enable J2EE sessions appropriately.
  • CFAdmin exposure
    • Risk: CFAdmin exposed publicly becomes a security liability.
    • Avoid: Bind CFAdmin to private networks only; IP-restrict or disable UI and use config-as-code.
  • Scheduled tasks duplication
    • Risk: Multiple replicas trigger duplicate jobs.
    • Avoid: Centralize schedulers, use single-replica for task runner, or implement distributed locks.
  • File uploads wiped on redeploy
    • Risk: Container filesystem is ephemeral; uploads vanish after restarts.
    • Avoid: Mount a volume or stream to S3/object storage.
  • Health checks too shallow
    • Risk: Container “healthy” but critical dependencies are down.
    • Avoid: Implement health.cfm that checks DB, cache, and external services.
  • JVM memory oversubscription
    • Risk: Container OOM if -Xmx doesn’t match container limits.
    • Avoid: Size heap to the container’s memory and monitor GC.
See also  How to Validate Security After ColdFusion Migration

Post-Migration Checklist

  • Security and access
    • CFAdmin disabled or restricted; default passwords changed; RDS disabled.
    • TLS termination at proxy; strong cipher suites; HSTS and needed headers applied.
  • Functionality
    • All routes return expected responses; no 404/500 errors in logs.
    • Datasource connections succeed; transactions commit/rollback correctly.
    • File uploads/downloads work and persist after restarts.
    • PDF/report generation renders with proper fonts/locale.
    • Email notifications reach the correct SMTP and are delivered.
    • Scheduled tasks run at expected times (time zone verified).
  • Performance
    • Load test passes baseline SLAs; no Slow queries or thread deadlocks.
    • JVM heap and metaspace within limits; GC times stable.
  • Observability
    • Application logs visible in log system; error alerts configured.
    • Health endpoints integrated into load balancer checks.
  • Resilience
    • Rolling restart doesn’t drop user sessions (or behavior understood).
    • Scale-out to N replicas still passes synthetic tests.
  • Compliance and governance
    • Licenses applied; image scans clean; SBOM generated.
    • Dockerfile and compose stored in Version control; tags pinned (no “latest”).

Validation Steps (Suggested)

  1. Smoke test the top 10 user flows end-to-end.
  2. Hit health.cfm repeatedly while restarting the container; confirm zero-downtime behind proxy.
  3. Force fail DB/network dependencies; ensure errors are logged and recover on retry.
  4. Run automated tests in CI against the container image.
  5. Simulate a scale-out to two replicas; verify session continuity or re-auth behavior is acceptable.
  6. Trigger all scheduled tasks; verify no duplicates fire unexpectedly.
  7. Rotate secrets and confirm the app reloads them (or redeploys) without manual downtime.

Example Nginx hardening snippets

Add in nginx Server config as appropriate

add_header X-Content-Type-Options nosniff always;
add_header X-Frame-Options SAMEORIGIN always;
add_header Referrer-Policy strict-origin-when-cross-origin always;

Enable HSTS only if all subdomains are HTTPS

add_header Strict-Transport-Security “max-age=31536000; includeSubDomains” always;

Deny access to CFAdmin if you must expose the container network

location ~* ^/(CFIDE|cfide|CFAdmin|railo-admin|lucee/admin) {
deny all;
return 404;
}


Practical tips and patterns

  • Keep application code read-only in the runtime image; use volumes only for mutable data.
  • Use multi-stage builds to run tests and produce lean final images.
  • Prefer environment variable interpolation in configuration files for portability.
  • Pin image versions to avoid accidental upgrades; schedule regular base image updates.
  • Replace fragile on-server Cron jobs with containerized task runners or orchestrator CronJobs.
  • Document everything: Dockerfile, compose, runbooks, rollback steps.

Minimal migration timeline (example)

  • Week 1: Inventory, export configuration, Dockerfile draft, local compose stack.
  • Week 2: Package installs, health endpoints, Nginx reverse proxy, initial tests.
  • Week 3: CI/CD, vulnerability scans, staging load tests, security review.
  • Week 4: Blue/green production rollout, monitoring and tuning, post-migration signoff.

FAQ

How do I migrate datasources without clicking through the Admin UI?

Use configuration-as-code. For Adobe ColdFusion, export via cfsetup from the source server and import in the container (or script with the CFAdmin API). For Lucee, encode datasources in server.json/web.json with environment interpolation. Install required JDBC packages in build steps so connections work out of the box.

Can I run Adobe ColdFusion and Lucee side-by-side during migration?

Yes. Run separate containers and route a subset of traffic (or specific paths) to a Lucee container for compatibility testing. Keep datasources read-only where necessary and compare outputs. This “canary” pattern reduces risk when swapping engines or versions.

How should I handle user sessions in a scaled container setup?

Externalize sessions to Redis or another distributed store. Enable J2EE sessions (Adobe) or configure Lucee session storage to Redis. Avoid sticky sessions if possible; they reduce resilience and complicate scaling. Validate login flows across rolling restarts and multi-replica deployments.

What’s the best way to secure CFAdmin in containers?

Don’t expose it publicly. Bind CFAdmin to an internal network only, protect with IP allowlists or VPN, and prefer configuration-as-code and automated imports over manual UI changes. Disable RDS, change default passwords, and monitor access logs for anomalies.

Do I need Kubernetes to benefit from containerizing ColdFusion?

No. Docker Compose is enough for local and small deployments. As your needs grow—multi-node scaling, self-healing, rolling updates—Kubernetes or a managed orchestrator becomes valuable. Your Dockerfile and image remain the foundation, so the investment carries forward.

About the author

Aaron Longnion

Aaron Longnion

Hey there! I'm Aaron Longnion — an Internet technologist, web software engineer, and ColdFusion expert with more than 24 years of experience. Over the years, I've had the privilege of working with some of the most exciting and fast-growing companies out there, including lynda.com, HomeAway, landsofamerica.com (CoStar Group), and Adobe.com.

I'm a full-stack developer at heart, but what really drives me is designing and building internet architectures that are highly scalable, cost-effective, and fault-tolerant — solutions built to handle rapid growth and stay ahead of the curve.