Running Lucee on AWS is not a single architecture. It is a set of choices about how much control you want, how you patch, how you route traffic, and where you draw the line between application, data, and operations.
The good news is that Lucee has practical deployment paths on AWS today. You can start with a pre-built AMI, build your own EC2-based Unix server, or run official Lucee containers on ECS or Fargate. The better news is that all three can share the same AWS data and operations foundation if you design it cleanly.
This guide walks through the options, what is actually running under the hood, and how to lay out the RDS and ops layers so the environment is maintainable after the first successful deployment.

1. Choose the AWS run pattern for Lucee
Start by choosing the deployment model. This decision affects patching, scaling, cost behavior, and how your team troubleshoots production issues.
There are three strong patterns for Lucee on AWS:
Option | Best fit | Main tradeoff |
AWS Marketplace AMI | Fastest path to a working Lucee server | Less control over the base image and bundled versions |
Self-built EC2 on Unix | Teams that want full control over OS, web server, hardening, and patching | More setup and maintenance work |
ECS or Fargate containers | Teams standardizing on container delivery | Requires container image, registry, task definition, and externalized state |
The right choice often comes down to what the team already operates well.
If your team knows EC2, systemd, Nginx or Apache, and traditional server patching, EC2 may be the clearest path. If the team already ships services through ECR and ECS, containers fit naturally. If you need a working Lucee box quickly for a migration, test environment, or small production workload, the Marketplace AMI removes a lot of early setup.
A useful rule is this:
Put control where your team has skill. Do not choose containers just because they sound modern, and do not choose EC2 just because it feels familiar.
For many CFML applications, the best first architecture is not complex. A load balancer, one or more Lucee application instances, Amazon RDS for the database, S3 for shared file storage, CloudWatch for logs and metrics, and Secrets Manager or Systems Manager Parameter Store for secrets can carry a lot of real workloads.
2. Start fast with the AWS Marketplace AMI
The fastest path to a running Lucee server on AWS is the Ortus Solutions Lucee CFML Server listing in AWS Marketplace. As described in the brief, the published image bundles a full stack:
Ubuntu Server 24.04 LTS as the base operating system
Lucee 5.4.6.9 as the CFML engine
Tomcat 9.x as the servlet container, with a hardened configuration
Nginx in front as a reverse proxy for static content and TLS termination
CommandBox 6 for application and package management from the command line
Security hardening based on the Adobe ColdFusion Lockdown Guide and the CIS Benchmark Level 1 for Linux
That stack matters because Lucee itself is not the whole server. A real production host needs an operating system, a JVM, a servlet container, a web tier, a service manager, logging, permissions, TLS handling, and a way to deploy application code.
With the Marketplace AMI, most of that is already assembled.
A simple AMI-based layout looks like this:
```text
User
|
Amazon Route 53
|
Application Load Balancer
|
EC2 instance from Lucee AMI
|
Amazon RDS
```
For a small environment, one EC2 instance may be enough. For production, place the instance in a private subnet behind an Application Load Balancer. Use the load balancer for public ingress and health checks. Keep direct SSH access restricted, ideally through AWS Systems Manager Session Manager instead of opening port 22 to broad IP ranges.
When launching the AMI, set the basics carefully:
Put the instance in a VPC with public and private subnets.
Attach a security group that allows web traffic only from the load balancer.
Allow database traffic only from the Lucee instance security group to the RDS security group.
Use an IAM role for AWS service access instead of static AWS keys.
Send system and application logs to CloudWatch Logs.
Store secrets outside the application code.
The AMI is the quickest on-ramp, but do not treat it as disposable magic. You still need a patch plan. Track OS updates, Lucee updates, Tomcat updates, Java updates, Nginx updates, and module or package updates. Before using it in production, verify the current Marketplace listing details, because published versions can change.
3. Build Lucee yourself on a Unix EC2 instance
The self-built path gives more control. It is a good fit when you need your own web server pattern, your own hardening baseline, a specific Java choice, a controlled Lucee version, or strict change windows.
The official Lucee Installer for Linux is designed for this model. It bundles Lucee, Tomcat, and an OpenJDK build into one package. It also lets you configure which system user runs the Lucee service.
A typical flow looks like this:
Launch a supported Linux EC2 instance.
Patch the base operating system.
Install the Lucee Linux installer package.
Configure the Lucee service user.
Place Nginx or Apache in front of Tomcat.
Lock down Tomcat so it is not directly exposed to the internet.
Configure logging, backups, and monitoring.
Deploy application code from a repeatable source.
This model is more work, but it removes uncertainty. You know exactly what changed because you built it.
Understand what Tomcat does in this stack
Lucee is a CFML engine that runs on the JVM. In a common Unix install, Tomcat provides the servlet container. The web server, such as Nginx or Apache, accepts HTTP and HTTPS traffic, handles static assets if configured that way, and forwards dynamic requests to Tomcat.
A simplified request path looks like this:
```text
Browser
|
Nginx or Apache
|
Tomcat
|
Lucee
|
CFML application code
|
Database, cache, file storage, and external services
```
Nginx and Apache are not required for the same reason in every setup. Nginx is often used as a high-performance reverse proxy in front of Tomcat. Apache is often used in environments that already depend on Apache configuration patterns, virtual hosts, or modules.
For teams putting Lucee behind Apache, `mod_cfml` handles multi-host routing. That lets a single Tomcat instance serve several CFML sites. This is useful for legacy CFML estates where one server may host multiple applications or domains.
Keep the service account boring and restricted
The Lucee service should run as a dedicated system user with only the permissions it needs. Avoid running application services as root. Keep writable directories intentional:
Application upload directories
Temporary directories
Log directories
Cache directories
Any generated asset directories
If multiple applications share a server, avoid letting one site write into another site’s code or configuration paths. This is one of the quiet benefits of containers, but EC2 can be made safe with disciplined filesystem permissions.

4. Run Lucee containers on ECS or Fargate
For container-based teams, the official `lucee/lucee` images from the official `lucee-dockerfiles` repository are the right starting point.
As currently maintained according to the brief:
The latest stable tag tracks Lucee 6.2.x on Tomcat 10.1 with Java 21 using Eclipse Temurin builds.
The LTS tag tracks Lucee 6.0.x on Tomcat 9.0 with Java 11.
That version difference matters. Tomcat 10.1 uses the newer Jakarta namespace world, while Tomcat 9.0 remains aligned with older servlet-era expectations. Before moving an existing CFML application to a newer Lucee and Tomcat combination, test the application, libraries, extensions, and servlet integrations.
The AWS-side deployment shape is familiar:
```text
Source code
|
Build container image
|
Push image to Amazon ECR
|
Run ECS task on Fargate
|
Connect to RDS, S3, CloudWatch, and Secrets Manager
```
This is similar to the pattern AWS documents for Adobe ColdFusion containers. The CFML engine differs, but the AWS container mechanics are the same.
A practical ECS layout includes:
Amazon ECR for the application image
ECS service for long-running Lucee tasks
Fargate to avoid managing EC2 container hosts
Application Load Balancer for ingress
CloudWatch Logs for container logs
Secrets Manager or Parameter Store for passwords and API keys
RDS in private subnets
S3 or EFS for shared files, depending on access pattern
Build containers as immutable application units
A common mistake is treating a container like a small virtual machine. Avoid shelling into containers and changing code by hand. Build the application into the image or mount only well-defined external paths.
Good container practice for Lucee means:
Package application code during the image build or pull it during a controlled startup process.
Keep environment-specific config outside the image.
Send logs to stdout, stderr, or a known log path collected by CloudWatch.
Store uploads and shared generated files outside the container.
Use health checks that prove the CFML app is actually responding.
For simple apps, the container image can include the full CFML codebase. For larger estates, the image may contain the runtime and startup logic, while the deployment process attaches code or artifacts in a controlled way.
Externalize state before scaling tasks
Fargate tasks can stop, restart, and move. That is a feature, but it means local disk is not a safe place for durable application state.
Move these out of the task:
User uploads
Generated PDFs or reports that need to persist
Session state if users must survive task replacement
Scheduled job coordination
Application secrets
Database credentials
Runtime configuration
Use S3 for object-style storage. Use EFS only when the application truly needs a shared filesystem. Use RDS for relational data. Use ElastiCache if session or cache sharing becomes necessary.
5. Lay out the RDS data layer
The database layer should be designed separately from the Lucee runtime. Whether Lucee runs on the Marketplace AMI, a custom EC2 instance, or ECS, the RDS pattern is mostly the same.
The basic production pattern is:
```text
Private application subnet
|
Lucee runtime
|
RDS security group
|
Amazon RDS in private database subnets
```
Do not place RDS in a public subnet for normal application use. Keep the database private. Let only the Lucee runtime security group connect to the database port.
Choose the RDS engine based on the application
Lucee applications often connect to relational databases through JDBC. Amazon RDS supports several common engines, including MySQL, MariaDB, PostgreSQL, Oracle, and SQL Server. Amazon Aurora, compatible with MySQL or PostgreSQL, is also common for applications that fit its model.
Choose based on the existing application first:
Existing application uses | Common AWS target |
MySQL | Amazon RDS for MySQL or Aurora MySQL-Compatible |
MariaDB | Amazon RDS for MariaDB |
PostgreSQL | Amazon RDS for PostgreSQL or Aurora PostgreSQL-Compatible |
Microsoft SQL Server | Amazon RDS for SQL Server |
Oracle Database | Amazon RDS for Oracle |
Do not change database engines during the first cloud move unless there is a strong reason. A CFML migration already has enough moving parts. Change the runtime location first, prove behavior, then plan database modernization as a separate project.
Configure connectivity from Lucee to RDS
At the Lucee level, database connections are usually configured as datasources. Keep the actual credentials out of source code. Use one of these patterns:
Pull secrets from AWS Secrets Manager during deployment or startup.
Store non-secret settings in Systems Manager Parameter Store.
Inject values as environment variables for containers.
Use instance or task roles for AWS access where possible.
For EC2, the application can read secrets using the instance role. For ECS, the task can receive secrets through task definition configuration. Both approaches are better than committing credentials to a repository or baking them into an AMI.
Connection settings deserve attention. Set sensible limits for connection pools so a scale-out event does not overwhelm RDS. If ten application nodes each open too many database connections, the database may fail even though each node looks fine by itself.
Plan pool settings with the whole system in mind:
```text
Maximum app instances
x maximum connections per instance
= possible database connections
```
Leave room for admin tools, migrations, background jobs, monitoring, and emergency access.

6. Add backups, migrations, and data operations
A working database connection is only the start. The data layer also needs backup, restore, migration, and maintenance plans.
For RDS, start with these controls:
Automated backups with a retention period that matches business needs
Manual snapshots before risky changes
Multi-AZ deployment for higher availability where production needs it
Maintenance windows that match release practices
Parameter groups managed as part of infrastructure configuration
Database logs reviewed through CloudWatch where supported
Encryption at rest for production and sensitive environments
Backups are not complete until restore has been tested. Schedule restore tests into a non-production environment. Confirm that the application can start, authenticate, read expected records, write safe test records, and run key workflows.
Treat schema changes as deployments
CFML applications often grow through years of incremental database changes. In AWS, treat schema changes as first-class deployments. Use a migration tool or at least a controlled script process.
A safe migration pattern is:
Take a snapshot or confirm backup status.
Apply schema changes in a lower environment.
Run application smoke tests.
Apply changes during a known release window.
Keep rollback steps ready where possible.
Watch database metrics after release.
Avoid manual, undocumented production changes. They make later debugging harder, especially when application code, Lucee version, Java version, and database behavior all changed near the same time.
Plan for file data separately from database data
Many CFML applications use both database records and filesystem content. Uploads, generated reports, images, exports, and temporary documents may live on disk.
On EC2, it is easy to leave these files on the instance. That becomes a problem when you add a second node, replace an instance, or restore from an AMI.
Use one of these patterns:
File type | AWS service that often fits |
User uploads and downloadable assets | Amazon S3 |
Shared filesystem required by legacy code | Amazon EFS |
Temporary files that can be regenerated | Local instance or task storage |
Private reports with lifecycle rules | Amazon S3 with access controls |
S3 is usually the cleaner long-term target for object-like files. EFS can help when legacy code expects POSIX-style shared paths, but it should not become a dumping ground for everything.
7. Build the operations layer around logs, metrics, and patching
The operations layer is what turns a working Lucee server into a service that can survive production use.
At minimum, collect:
Web server access and error logs
Tomcat logs
Lucee application logs
System logs on EC2
Container logs on ECS
RDS metrics and database logs where available
Load balancer metrics
JVM memory and CPU indicators where practical
CloudWatch can hold much of this. The key is not just collection. Give logs useful names, set retention periods, and create alarms for failure patterns.
Useful alarms include:
High 5xx responses from the load balancer
Unhealthy target count
High EC2 CPU or memory pressure
Fargate task restarts
RDS CPU, storage, and connection pressure
Low free storage on EC2 volumes
Repeated application exceptions
Failed scheduled jobs
For EC2, patching must cover the OS, Java, Tomcat, Lucee, Nginx or Apache, and installed packages. For the Marketplace AMI, track the vendor’s update path and your own patch schedule. For self-built servers, automate as much as practical through infrastructure scripts or image builds.
For ECS, patching often means rebuilding images from updated base images, testing them, and rolling new task versions. That trades SSH-based maintenance for image-based maintenance.
Neither model removes operations work. Containers change the shape of the work.
8. Secure the runtime boundaries
Security starts with simple boundaries. Put public traffic through the load balancer. Keep application servers private when possible. Keep RDS private. Use security groups as narrow allow lists.
A basic production boundary looks like this:
```text
Internet
|
Application Load Balancer
|
Private Lucee runtime
|
Private RDS database
```
For TLS, terminate at the load balancer or at Nginx depending on the architecture. If TLS terminates before Tomcat, make sure the application understands the original protocol and host headers where needed. Many applications generate absolute URLs, redirects, or secure cookie settings based on request metadata.
Store secrets outside code. Rotate credentials where possible. Avoid using one shared database account for every task if the application and operations model supports more granular accounts.
For Lucee Administrator access, do not expose admin interfaces broadly. Restrict access by network, authentication, and operational process. In production, treat runtime admin access as a privileged activity, not a normal development convenience.
Also review outbound access. Many applications can run with limited outbound internet access if dependencies are known. If the application must reach payment providers, email providers, APIs, or package repositories, document those destinations.
9. Deploy application code in a repeatable way
The deployment method should match the run pattern.
For the Marketplace AMI or self-built EC2, common approaches include:
Pulling a versioned artifact from S3
Deploying from a CI system over a controlled channel
Building a new AMI for each release
Using CodeDeploy or a similar release tool
Using CommandBox for package and application tasks where appropriate
For ECS or Fargate, the cleaner pattern is:
Build the image.
Tag it with a version.
Push it to ECR.
Update the ECS service.
Let ECS replace tasks under load balancer health checks.
Roll back to the prior task definition if needed.
Avoid making production changes by hand on a running node. Manual fixes feel fast, but they create configuration drift. The next replacement instance or container will lose the change, or worse, only some nodes will have it.
A good Lucee deployment has three proofs:
The same artifact can be deployed again.
A new node can join without manual steps.
A failed release can be rolled back with known commands.

10. Validate the full stack before production
Before calling the environment ready, test more than the home page.
Run through a checklist that covers the complete stack:
DNS resolves to the intended load balancer.
TLS certificates work and renew through the chosen process.
Health checks reflect real application health.
Lucee can connect to RDS through the intended datasource.
Database credentials are not stored in source code.
Logs reach CloudWatch or the chosen log platform.
Application uploads persist after restart or replacement.
Sessions behave correctly across scale events.
Scheduled jobs do not run twice unless that is intended.
Backups exist and a restore has been tested.
Security groups allow only required traffic.
Admin interfaces are restricted.
Rollback has been tested at least once.
Pay special attention to scheduled tasks. Many CFML applications rely on internal schedulers. If you run two Lucee nodes, the same scheduled job may run twice unless you design around it. Use a single scheduler node, a database lock, an external scheduler, or an application-level coordination method.
Also test cold starts. Stop and replace an EC2 instance, or force ECS to replace a task. The new runtime should come up, read config, connect to RDS, find required storage, and pass the load balancer health check without a person logging in.
What success looks like
A good Lucee on AWS architecture is not defined by whether it uses AMIs, EC2, or ECS. It is defined by whether the pieces are clear and replaceable.
The runtime should be understandable. Nginx or Apache receives traffic, Tomcat hosts Lucee, Lucee runs CFML, and the application connects to managed AWS services for data, files, secrets, logs, and metrics.
The data layer should be private, backed up, and tested. RDS should not be an afterthought added once the server boots. It should have its own security groups, backup policy, maintenance plan, connection limits, and restore process.
The operations layer should make failure visible. Logs, metrics, alarms, patching, and deployment history matter as much as the first successful request.
If you need speed, start with the Marketplace AMI. If you need control, build Lucee on Unix EC2 with the official installer. If you need container delivery, use the official Lucee Docker images with ECS or Fargate. In all three cases, keep state out of the runtime, keep RDS private, and make every deployment repeatable.





