Docker Image Best Practices
Docker Image Best Practices
The official lucee/lucee images are a good base for your own image. This recipe collects what makes such an image fast to start, reproducible and safe to run. For the basics, see Docker. Ready to run examples live in the lucee-docs examples folder.
A complete example
# pin the exact Lucee version
FROM lucee/lucee:7.0.3.43
# server configuration: extensions, datasources, loggers, ...
COPY ./.CFConfig.json /opt/lucee/server/lucee-server/context/.CFConfig.json
# your application
COPY ./www /var/www/
# install extensions and resolve everything at build time, then exit
RUN LUCEE_ENABLE_WARMUP=true /usr/local/tomcat/bin/catalina.sh run
# the final container must not download anything at startup
ENV LUCEE_MAVEN_DOWNLOAD_POLICY_STARTUP=error
HEALTHCHECK --interval=30s --timeout=5s --start-period=60s \
CMD wget -q -O /dev/null http://localhost:8888/health.cfm || exit 1
The sections below explain each step.
Run warmup at build time
The most important step: run Lucee's Warmup in a RUN step after copying your configuration. Warmup starts Lucee once, installs the extensions defined in .CFConfig.json, calls onBuild in Server.cfc (see onBuild Function in Server.cfc) and exits. Everything it installs and downloads ends up in the image layer, so the container starts without downloading anything.
Warmup is enabled with the environment variable LUCEE_ENABLE_WARMUP=true or the system property lucee.enable.warmup (lucee.build / LUCEE_BUILD is the legacy alias). The official images already run warmup once while they are built, so the base image itself is warm. Your image needs its own warmup step because your configuration adds things.
Only set the variable for the RUN step, never with ENV. With ENV, every container started from the image would run warmup and then exit.
Java libraries from javasettings are resolved when they are first used, not at startup. To bake them in too, load them in onBuild, for example with mavenLoad() or mavenImport(). See Loading Java Libraries with Maven.
For more on where extensions come from, and how to download .lex files yourself (Maven, Docker multistage builds, curl), see Downloading Extensions for CI, Docker and Offline Installs.
Pin versions
- Use an exact Lucee tag such as
lucee/lucee:7.0.3.43instead oflatestor7.0. Floating tags change under you, and a rebuild can then give you a different Lucee version. - Pin extension versions in
.CFConfig.json(orLUCEE_EXTENSIONS). Without a version, Lucee resolves the latest release, which can change between builds. See Extension Installation. - Prefer final releases. Snapshots and pre-releases have a limited lifespan in the Maven repositories.
Configuration: .CFConfig.json and environment variables
Copy your server configuration to /opt/lucee/server/lucee-server/context/.CFConfig.json, as all examples in the examples folder do. Keep values that differ between environments (hostnames, credentials, URLs) out of the file and use placeholders such as ${DB_HOST}, which Lucee fills in from environment variables (as the quartz-ai example does for its AI settings).
Settings that Lucee reads from environment variables or system properties can also be set with ENV in the Dockerfile or at docker run. See Environment Variables / System Properties for Lucee.
Remember that extensions in .CFConfig.json are only processed on a fresh install, which is exactly what the warmup step does.
Maven download policies
Lucee has two settings that control whether it may download Maven artifacts. Which one applies depends on whether the engine has finished starting:
| Setting | Applies to |
|---|---|
LUCEE_MAVEN_DOWNLOAD_POLICY_STARTUP (lucee.maven.download.policy.startup) |
downloads while Lucee is starting up, for example extensions from .CFConfig.json |
LUCEE_MAVEN_DOWNLOAD_POLICY_RUNTIME (lucee.maven.download.policy.runtime) |
downloads after startup, for example javasettings libraries or mavenLoad() |
Both accept ignore (default), warn (download and log) or error (don't download, fail instead). In Lucee 8 they can also be set in .CFConfig.json as maven.downloadPolicyStartup and maven.downloadPolicyRuntime.
For a warmed image, set LUCEE_MAVEN_DOWNLOAD_POLICY_STARTUP=error in the final image (after the warmup step, as in the example above). If something wasn't baked in, startup fails right away instead of silently downloading it. Use warn for the runtime policy first to see what your application still downloads, and switch it to error once that list is empty.
Multistage builds
If you'd rather ship .lex files than let warmup fetch them, download them in a separate build stage and copy only the result into the Lucee image. That keeps build tools such as Maven out of the final image. Downloading Extensions for CI, Docker and Offline Installs has a complete multistage example.
Healthchecks
The official images don't define a HEALTHCHECK. Add your own, pointing at a small page of your application that checks what matters to you (for example the database). The images include wget, so you don't have to install anything. Allow a --start-period that covers Lucee's startup.
Logging to stdout
Containers should log to stdout so that docker logs and your log platform see everything. Since Lucee 6.2 you can redirect all logs to the console with environment variables:
ENV LUCEE_LOGGING_FORCE_APPENDER=console
ENV LUCEE_LOGGING_FORCE_LEVEL=info
Alternatively configure single loggers with the console appender in .CFConfig.json. See Console logging using SystemOutput.
Keep secrets out of the image
Everything in an image layer can be read by anyone who has the image, including values set with ENV or copied files that a later layer deletes. So:
- don't put passwords, API keys or tokens into the Dockerfile or
.CFConfig.json; pass them in at runtime with environment variables or mounted files and reference them with placeholders - set
LUCEE_ADMIN_PASSWORDatdocker run/ in your compose file, not in the image - for more control, use a secret provider (environment variables, files, AWS Secrets Manager, ...). See Secret Management and the secret-provider example.
Related
- Docker
- Warmup
- onBuild Function in Server.cfc
- Downloading Extensions for CI, Docker and Offline Installs
- Extension Installation
- Console logging using SystemOutput
- Secret Management