Skip to main content
Advertisement

Virtual Hosts and Web Application Deployment

Tomcat supports multiple virtual hosts through Host configuration and can deploy WAR files or directories automatically or manually. This chapter covers the most common deployment patterns and safe operational practices.


Deployment Methods

Tomcat offers three deployment methods.

MethodHowCharacteristics
Auto DeployCopy WAR to webapps/Simple, not recommended for production
Manual DeployCreate Context XML fileExplicit, recommended
Manager AppHTTP PUT or Web UICI/CD integration possible

WAR File Auto Deployment

With autoDeploy="true" set on a Host, copying a WAR file to the webapps/ directory triggers automatic deployment.

# Copy WAR file to deployment directory
sudo cp myapp-1.2.0.war /opt/tomcat/latest/webapps/myapp.war

# Tomcat automatically extracts it
# /opt/tomcat/latest/webapps/myapp/ ← created

# Access URL: http://host:8080/myapp/

Auto Deployment Settings (server.xml)

<Host name="localhost" appBase="webapps"
unpackWARs="true"
autoDeploy="true"
deployOnStartup="true">
AttributeDescriptionProduction Recommendation
unpackWARsAuto-extract WARtrue
autoDeployAuto-detect/deploy WARs at runtimefalse
deployOnStartupScan appBase on startuptrue

Production warning: autoDeploy="true" causes Tomcat to periodically scan the filesystem. An incompletely copied WAR can be deployed mid-copy, so autoDeploy="false" is recommended for production.


Create Context XML files in conf/Catalina/localhost/ for explicit deployment path control.

$CATALINA_HOME/conf/
└── Catalina/
└── localhost/
├── ROOT.xml → / (root context)
├── myapp.xml → /myapp
└── api.xml → /api

Basic Context XML

<!-- conf/Catalina/localhost/myapp.xml -->
<Context docBase="/var/www/myapp"
path="/myapp"
reloadable="false"
crossContext="false"
privileged="false">
</Context>

With JNDI DataSource

<!-- conf/Catalina/localhost/myapp.xml -->
<Context docBase="/var/www/myapp"
path="/myapp"
reloadable="false">

<Resource name="jdbc/myDB"
auth="Container"
type="javax.sql.DataSource"
factory="org.apache.tomcat.jdbc.pool.DataSourceFactory"
driverClassName="com.mysql.cj.jdbc.Driver"
url="jdbc:mysql://db-host:3306/mydb?useSSL=false&amp;serverTimezone=Asia/Seoul"
username="appuser"
password="AppP@ssw0rd"
maxTotal="50"
maxIdle="10"
minIdle="5"
maxWaitMillis="30000"
validationQuery="SELECT 1"
testOnBorrow="true"
removeAbandoned="true"
removeAbandonedTimeout="60"
logAbandoned="true"/>
</Context>

Replacing the ROOT Context (Deploy at Root Path)

<!-- conf/Catalina/localhost/ROOT.xml -->
<Context docBase="/var/www/myapp/current"
reloadable="false">
</Context>

This serves the app in /var/www/myapp/current at http://host:8080/.


Multiple Virtual Host Configuration

server.xml

<Engine name="Catalina" defaultHost="localhost">

<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="false">
</Host>

<Host name="app1.example.com" appBase="/var/www/app1"
unpackWARs="true" autoDeploy="false">
<Alias>www.app1.example.com</Alias>
<Valve className="org.apache.catalina.valves.AccessLogValve"
directory="logs"
prefix="app1_access_log"
suffix=".txt"
pattern="%h %l %u %t &quot;%r&quot; %s %b %D"/>
</Host>

<Host name="app2.example.com" appBase="/var/www/app2"
unpackWARs="true" autoDeploy="false">
</Host>

</Engine>

Context File Structure per Virtual Host

conf/
└── Catalina/
├── localhost/
│ └── ROOT.xml
├── app1.example.com/
│ └── ROOT.xml → root context for app1
└── app2.example.com/
└── ROOT.xml → root context for app2

Deployment via Tomcat Manager

Manager App Account Setup

<!-- conf/tomcat-users.xml -->
<?xml version="1.0" encoding="UTF-8"?>
<tomcat-users>
<role rolename="manager-gui"/>
<role rolename="manager-script"/>
<role rolename="admin-gui"/>
<user username="admin"
password="MyStr0ngP@ssw0rd!"
roles="manager-gui,manager-script,admin-gui"/>
</tomcat-users>

Restrict Manager Access by IP

<!-- webapps/manager/META-INF/context.xml -->
<Context antiResourceLocking="false" privileged="true">
<CookieProcessor className="org.apache.tomcat.util.http.Rfc6265CookieProcessor"
sameSiteCookies="strict"/>
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.0\.0\.1|192\.168\.1\.\d+"/>
</Context>

Remote Deployment via curl (CI/CD)

# Deploy WAR file
curl -u admin:MyStr0ngP@ssw0rd! \
"http://localhost:8080/manager/text/deploy?path=/myapp&update=true" \
--upload-file /path/to/myapp.war

# List deployed applications
curl -u admin:MyStr0ngP@ssw0rd! \
"http://localhost:8080/manager/text/list"

# Undeploy application
curl -u admin:MyStr0ngP@ssw0rd! \
"http://localhost:8080/manager/text/undeploy?path=/myapp"

# Restart
curl -u admin:MyStr0ngP@ssw0rd! \
"http://localhost:8080/manager/text/restart?path=/myapp"

Zero-Downtime Deployment Patterns

Pattern 1: WAR Replacement + Restart

#!/bin/bash
set -e

CATALINA_HOME=/opt/tomcat/latest
APP_NAME=myapp
WAR_FILE=/tmp/myapp-1.2.0.war

# 1. Backup previous WAR
if [ -f "$CATALINA_HOME/webapps/${APP_NAME}.war" ]; then
cp "$CATALINA_HOME/webapps/${APP_NAME}.war" \
"/var/backup/${APP_NAME}-$(date +%Y%m%d-%H%M%S).war"
fi

# 2. Remove old deployment (prevent stale classes)
rm -rf "$CATALINA_HOME/webapps/${APP_NAME}"
rm -f "$CATALINA_HOME/webapps/${APP_NAME}.war"
rm -rf "$CATALINA_HOME/work/Catalina/localhost/${APP_NAME}"

# 3. Copy new WAR
cp "$WAR_FILE" "$CATALINA_HOME/webapps/${APP_NAME}.war"

# 4. Restart Tomcat
sudo systemctl restart tomcat

echo "Deployment complete: ${APP_NAME}"

Pattern 2: Blue-Green Deployment (with Nginx)

#!/bin/bash
# Blue-Green deployment (Nginx + 2 Tomcat instances)

NEW_WAR=/tmp/myapp-1.2.0.war
ACTIVE=$(cat /var/run/active-tomcat)

if [ "$ACTIVE" = "blue" ]; then
TARGET_PORT=8081
NEW_ACTIVE="green"
else
TARGET_PORT=8080
NEW_ACTIVE="blue"
fi

# Deploy to inactive instance
cp "$NEW_WAR" "/opt/tomcat-${NEW_ACTIVE}/webapps/ROOT.war"
systemctl restart "tomcat-${NEW_ACTIVE}"

# Health check
sleep 10
HTTP_STATUS=$(curl -s -o /dev/null -w "%{http_code}" \
http://localhost:${TARGET_PORT}/actuator/health)

if [ "$HTTP_STATUS" = "200" ]; then
# Switch Nginx
sed -i "s/proxy_pass http:\/\/localhost:[0-9]*/proxy_pass http:\/\/localhost:${TARGET_PORT}/" \
/etc/nginx/sites-available/myapp.conf
nginx -s reload

echo "$NEW_ACTIVE" > /var/run/active-tomcat
echo "✅ Deployment successful: ${NEW_ACTIVE} (port ${TARGET_PORT})"
else
echo "❌ Health check failed — rolling back"
exit 1
fi

Post-Deployment Verification

# Check deployment status in Tomcat logs
tail -100 /opt/tomcat/latest/logs/catalina.out | grep -E "(deploy|error|warn)" -i

# Successful deployment message
# INFO [main] org.apache.catalina.startup.HostConfig.deployWAR
# Deployment of web application archive [.../myapp.war] has finished in X ms

# Access test
curl -s -o /dev/null -w "%{http_code}" http://localhost:8080/myapp/
# 200

# List deployed apps via Manager API
curl -u admin:password http://localhost:8080/manager/text/list

Summary

ItemDetails
Default deploy path$CATALINA_HOME/webapps/
Recommended deploy methodContext XML file + autoDeploy="false"
ROOT contextconf/Catalina/localhost/ROOT.xml
Manager securityStrong password + IP restriction
CI/CD integrationManager REST API (via curl)
Zero-downtimeBlue-Green pattern + Nginx port switching
Advertisement