Note
Access to this page requires authorization. You can try signing in or changing directories.
Access to this page requires authorization. You can try changing directories.
This guide walks you through the process of migrating an existing Tomcat application to Azure Container Apps. It covers pre-migration assessment, the migration itself, and post-migration optimization.
Prerequisites
- An Azure subscription. If you don't have one, create a free account.
- Azure CLI installed, or access to the Azure portal.
- Familiarity with Apache Tomcat administration, WAR packaging, and Docker containers.
- A supported JDK version (8, 11, 17, or 21). For more information, see Java on Azure Container Apps overview.
- Docker (optional - only needed if you build images locally).
pre-migration assessment
Before you start the migration, complete the assessment and inventory steps described in the following sections.
Inventory external resources
You inject external resources, such as data sources, JMS message brokers, and others, by using Java Naming and Directory Interface (JNDI). Some of these resources might require migration or reconfiguration.
Inside your application
Inspect the META-INF/context.xml file. Look for <Resource> elements inside the <Context> element.
On the application servers
Inspect the $CATALINA_BASE/conf/context.xml and $CATALINA_BASE/conf/server.xml files. Also inspect the .xml files in $CATALINA_BASE/conf/<engine-name>/<host-name> directories.
In context.xml files, you describe JNDI resources by using <Resource> elements inside the top-level <Context> element.
In server.xml files, you describe JNDI resources by using <Resource> elements inside the <GlobalNamingResources> element.
Data sources
Data sources are JNDI resources with the type attribute set to javax.sql.DataSource. For each data source, document the following information:
- What is the data source name?
- What is the connection pool configuration?
- Where is the JDBC driver JAR file?
For more information, see JNDI Datasource HOW-TO in the Tomcat documentation.
All other external resources
It's not feasible to document every possible external dependency in this guide. Your team is responsible for verifying that you can satisfy every external dependency of your application after the migration.
Inventory secrets and certificates
Passwords and secure strings
Check all properties and configuration files on the production servers for any secret strings and passwords. Be sure to check server.xml and context.xml in $CATALINA_BASE/conf. You might also find configuration files containing passwords or credentials inside your application, including META-INF/context.xml and, for Spring Boot applications, application.properties or application.yml files.
Certificates
Document all the certificates used for public SSL endpoints or communication with backend databases and other systems. You can view all certificates on the production servers by running the following command:
keytool -list -v -keystore <path to keystore>
Review file system usage
Identify any instances where your services read from or write to the local file system. Note which files are short-term or temporary and which files are long-lived.
Azure Container Apps offers several types of storage. By using ephemeral storage, you can read and write temporary data within a running container or replica. By using Azure Files, you can provide permanent storage that multiple containers can share. For more information, see Use storage mounts in Azure Container Apps.
If your application serves read-only static content, consider moving it to Azure Blob Storage and adding Azure CDN for global distribution. For more information, see Static website hosting in Azure Storage and Quickstart: Integrate an Azure storage account with Azure CDN.
If your application handles dynamically published static content (uploaded or generated content that doesn't change after creation), you can integrate Azure Blob Storage and Azure CDN. You can also use an Azure Function to manage uploads and trigger CDN refreshes. For a sample implementation, see Uploading and CDN-preloading static content with Azure Functions.
If your application currently serves static content from the Tomcat webapps/ directory, plan to move that content to an external storage solution as part of the migration.
Check for OS-specific code
If your application contains code with dependencies on the host OS, refactor it to remove those dependencies. For example, replace any use of / or \ in file system paths with File.Separator or Paths.get.
Verify platform compatibility
If you manually create your Dockerfile and deploy a containerized application to Azure Container Apps, you have full control over your deployment, including JRE/JDK versions and Tomcat versions.
Before you create container images, migrate your application to the JDK and Tomcat versions that you intend to use on Container Apps. Test your application thoroughly to ensure compatibility and performance.
Note
This validation is especially important if your current server runs on an unsupported JDK, such as Oracle JDK or IBM OpenJ9.
To check your current Java version, sign in to your production server and run the following command:
java -version
Identify session persistence mechanism
To identify the session persistence manager in use, inspect the context.xml files in your application and Tomcat configuration. Look for the <Manager> element, and then note the value of the className attribute.
Tomcat's built-in PersistentManager implementations, such as StandardManager or FileStore, aren't designed for use with a distributed, scaled platform such as Azure Container Apps. Container Apps might load balance among several instances and transparently restart any instance at any time, so persisting mutable state to a file system isn't recommended.
If you require session persistence, use an alternate PersistentManager implementation that writes to an external data store, such as VMware Tanzu Session Manager with Redis Cache.
Identify scheduled jobs
You can't use scheduled jobs, such as Quartz Scheduler tasks or cron jobs, with containerized Tomcat deployments. If you scale out your application, one scheduled job might run more than once per scheduled period, which can lead to unintended consequences.
Inventory any scheduled jobs, inside or outside the application server. Short-lived or batch-style tasks are good candidates for Container Apps jobs. For more information, see Jobs in Azure Container Apps.
Determine whether MemoryRealm is used
MemoryRealm requires a persisted XML file. On Azure Container Apps, you need to add this file to the container image or upload it to shared storage that you make available to containers. For more information, see the Identify session persistence mechanism section. You must modify the pathName parameter accordingly.
To determine whether MemoryRealm is currently used, inspect your server.xml and context.xml files. Search for <Realm> elements where the className attribute is set to org.apache.catalina.realm.MemoryRealm.
Parameterize the configuration
During the pre-migration, you likely identified secrets and external dependencies, such as data sources, in server.xml and context.xml files. For each item, replace any username, password, connection string, or URL with an environment variable.
Note
Use the most secure authentication flow available. The authentication flow described in this procedure, such as for databases, caches, messaging, or AI services, requires a very high degree of trust in the application and carries risks not present in other flows. Use this flow only when more secure options, like managed identities for passwordless or keyless connections, aren't viable. For local machine operations, prefer user identities for passwordless or keyless connections.
For example, suppose the context.xml file contains the following element:
<Resource
name="jdbc/dbconnection"
type="javax.sql.DataSource"
url="jdbc:postgresql://postgresdb.contoso.com/wickedsecret?ssl=true"
driverClassName="org.postgresql.Driver"
username="postgres"
password="{password}"
/>
You can change it as shown in the following example:
<Resource
name="jdbc/dbconnection"
type="javax.sql.DataSource"
url="${postgresdb.connectionString}"
driverClassName="org.postgresql.Driver"
username="${postgresdb.username}"
password="${postgresdb.password}"
/>
Assess logging and APM
Identify any log aggregation solutions that the applications you're migrating use. You need to configure diagnostic settings during migration so that logged events are available for consumption. For more information, see the Configure logging and diagnostics section.
Identify any application performance management (APM) agents that your applications use. Azure Container Apps doesn't offer built-in APM support. Prepare your container image or integrate the APM tool directly into your code. If you want to measure your application's performance but have yet to integrate any APM yet, consider using Azure Application Insights with the auto-instrumentation Java agent. For more information, see Enable Azure Monitor OpenTelemetry for Java applications.
Document deployment architecture
Document the following information for your Tomcat application:
- The number of instances running.
- The number of CPUs allocated to each instance.
- The amount of RAM allocated to each instance.
Also determine whether you distribute your application instances among several regions or data centers. Document the uptime requirements and SLA for the applications you're migrating.
Migration
Create a Container Apps environment
Create a Container Apps environment in your Azure subscription. For more information, see Quickstart: Deploy your first container app using the Azure portal.
Configure logging and diagnostics
Configure your logging to route all output to the console instead of to files.
After you deploy the application to Azure Container Apps, you can configure the logging options within your Container Apps environment to define one or more log destinations. These destinations can include Azure Monitor Log Analytics, Azure Event Hubs, or non-Microsoft monitoring solutions. You can also disable log data storage and view logs only at runtime. For configuration instructions, see Log storage and monitoring options in Azure Container Apps.
Configure persistent storage
If any part of your application reads or writes to the local file system, configure persistent storage to replace it. For example, if your Tomcat application writes logs or uploads to /opt/tomcat/data, create an Azure Files share and mount it to the same path:
az containerapp update \
--resource-group <RESOURCE_GROUP> \
--name <APP_NAME> \
--set-env-vars "UPLOAD_DIR=/opt/tomcat/data"
Specify the path to mount in the container through the app settings and align it with the path your application uses. For more information, see Use storage mounts in Azure Container Apps.
Migrate certificates to Azure Key Vault
Azure Container Apps supports secure communication between apps. Your application doesn't need to manage the process of establishing secure communication. You can upload a private certificate to Azure Container Apps or use a free managed certificate. Using Azure Key Vault to manage certificates is the recommended approach.
To store a certificate in Key Vault and reference it from your container app:
- Import the certificate into Azure Key Vault. For more information, see Import a certificate in Azure Key Vault.
- Enable a managed identity on your container app and grant it the Key Vault Secrets User role on the vault.
- Configure the container app to use the Key Vault certificate for custom domains.
For more information, see Certificates in Azure Container Apps.
Prepare the deployment artifacts
Clone the Tomcat on Containers Quickstart GitHub repository. This repository contains a Dockerfile and Tomcat configuration files with many recommended optimizations. The following steps outline modifications you likely need to make to these files before building the container image and deploying to Container Apps.
Note
Some Tomcat deployments run multiple applications on a single Tomcat server. If this setup matches your deployment, run each application in a separate container app. By using this approach, you can optimize resource utilization for each application while minimizing complexity and coupling.
Add JNDI resources
Edit server.xml to add the resources you prepared in the pre-migration steps, such as data sources, as shown in the following example:
Note
Use the most secure authentication flow available. The authentication flow described in this procedure, such as for databases, caches, messaging, or AI services, requires a high degree of trust in the application and carries risks not present in other flows. Use this flow only when more secure options, like managed identities for passwordless, or keyless connections, aren't viable. For local machine operations, prefer user identities for passwordless or keyless connections.
<!-- Global JNDI resources
Documentation at /docs/jndi-resources-howto.html
-->
<GlobalNamingResources>
<!-- Editable user database that can also be used by
UserDatabaseRealm to authenticate users
-->
<Resource name="UserDatabase" auth="Container"
type="org.apache.catalina.UserDatabase"
description="User database that can be updated and saved"
factory="org.apache.catalina.users.MemoryUserDatabaseFactory"
pathname="conf/tomcat-users.xml"
/>
<!-- Migrated datasources here: -->
<Resource
name="jdbc/dbconnection"
type="javax.sql.DataSource"
url="${postgresdb.connectionString}"
driverClassName="org.postgresql.Driver"
username="${postgresdb.username}"
password="${postgresdb.password}"
/>
<!-- End of migrated datasources -->
</GlobalNamingResources>
For more data source instructions, see the following sections of the JNDI Datasource How-To in the Tomcat documentation:
Build and push the image
The simplest way to build and upload the image to Azure Container Registry (ACR) for use by Container Apps is to use the az acr build command. This command doesn't require Docker to be installed on your computer. For example, if you have the Dockerfile from the tomcat-container-quickstart repo and the application package petclinic.war in the current directory, you can build the container image in ACR with the following command:
az acr build \
--registry $acrName \
--image "${acrName}.azurecr.io/petclinic:{{.Run.ID}}" \
--build-arg APP_FILE=petclinic.war \
--build-arg SERVER_XML=prod.server.xml .
You can omit the --build-arg APP_FILE... parameter if your WAR file is named ROOT.war. You can omit the --build-arg SERVER_XML... parameter if your server XML file is named server.xml. Both files must be in the same directory as the Dockerfile.
Alternatively, you can use Docker CLI to build the image locally by using the following commands. This approach can simplify testing and refining the image before initial deployment to ACR. However, it requires Docker CLI to be installed and Docker daemon to be running.
# Build the image locally.
docker build . --build-arg APP_FILE=petclinic.war -t "${acrName}.azurecr.io/petclinic:1"
# Run the image locally.
docker run -d -p 8080:8080 "${acrName}.azurecr.io/petclinic:1"
# You can now access your application with a browser at http://localhost:8080.
# Sign in to ACR.
az acr login --name $acrName
# Push the image to ACR.
docker push "${acrName}.azurecr.io/petclinic:1"
Note
On Linux, you might need to prefix the docker commands with sudo if your user isn't in the docker group.
For more information, see Build and store container images with Azure Container Registry.
Deploy to Azure Container Apps
The following command shows an example deployment:
az containerapp create \
--resource-group <RESOURCE_GROUP> \
--name <APP_NAME> \
--environment <ENVIRONMENT_NAME> \
--image <IMAGE_NAME> \
--target-port 8080 \
--ingress 'external' \
--registry-server <REGISTRY_SERVER> \
--min-replicas 1
For a more in-depth quickstart, see Quickstart: Deploy your first container app.
Configure secrets and environment variables
Inject configuration settings into each application as environment variables. Set these variables as manual entries or as references to secrets. For more information, see Manage environment variables on Azure Container Apps and Manage secrets in Azure Container Apps.
Set up identity and authentication
If your Tomcat application requires authentication or authorization, ensure the configuration is set to access the identity provider:
- If the identity provider is Microsoft Entra ID, don't make any changes.
- If the identity provider is an on-premises Active Directory forest, consider implementing a hybrid identity solution with Microsoft Entra ID. For more information, see the Hybrid identity documentation.
- If the identity provider is another on-premises solution, such as PingFederate, see Custom installation of Microsoft Entra Connect to configure federation with Microsoft Entra ID.
If your application uses a Tomcat Realm for authentication (for example, MemoryRealm or JDBCRealm), plan to migrate to an external identity provider or configure the realm within your container image.
Expose the application
By default, an application deployed to Azure Container Apps isn't accessible from outside the environment. To enable external access, configure ingress:
az containerapp ingress enable \
--resource-group <RESOURCE_GROUP> \
--name <APP_NAME> \
--type external \
--target-port 8080 \
--transport auto
If you deploy your app in a managed environment with its own virtual network, determine the app's accessibility level to allow public ingress or ingress from your virtual network only. For more information, see Networking in Azure Container Apps environment.
Post-migration
After you complete the migration, verify that your application works as expected. The following sections describe recommendations for making your application more cloud-native and operationally robust.
Improve operational readiness
The following recommendations help you strengthen reliability, observability, and deployment practices for your migrated application.
- CI/CD pipelines: Add a deployment pipeline for automatic, consistent deployments. Instructions are available for Azure Pipelines and GitHub Actions.
- Blue-green deployment: Use container app revisions, revision labels, and ingress traffic weights to test code changes in production before making them available to end users. For more information, see Blue-Green Deployment in Azure Container Apps.
- Service bindings: Add service bindings to connect your application to supported Azure databases. Service bindings eliminate the need to provide connection information, including credentials, to your Spring Boot applications.
- JVM metrics: Enable the Java development stack to collect JVM core metrics. For more information, see Java metrics for Java apps in Azure Container Apps.
- Alerts: Add Azure Monitor alert rules and action groups to quickly detect and address aberrant conditions. For more information, see Set up alerts in Azure Container Apps.
- Zone redundancy: Replicate your app across availability zones by enabling zone redundancy. Traffic is load balanced and automatically routed to replicas if a zone outage occurs. For more information, see Reliability in Azure Container Apps.
- Web Application Firewall: Protect your container app from common exploits and vulnerabilities by using Web Application Firewall on Application Gateway. For more information, see Protect Azure Container Apps with Web Application Firewall on Application Gateway.
Tomcat-specific recommendations
To improve performance, evaluate the items in the logging.properties file. Consider eliminating or reducing some of the logging output.
Consider monitoring the code cache size and adding the parameters
-XX:InitialCodeCacheSizeand-XX:ReservedCodeCacheSizeto theJAVA_OPTSvariable in the Dockerfile to further optimize performance. For more information, see Codecache Tuning in the Oracle documentation.