TomEE as code: the server in the repository

Page content

Following a recipe (the POM), engineers fill a TomEE crate (the stock distribution), adding one jar and discarding another (libraries), laying parchments from a chest on top (the overlay) and pouring in vials (secrets) from a locked cabinet (a secret store); one key (one Maven command) brings a fresh server as the old one is swept away (a clean run)

If a new developer needs a setup guide to run your TomEE, the server is not in your repository yet. The TomEE Maven plugin, an overlay folder and environment variables put it there. One command then builds a clean server from the repository and starts the application on it, on any machine.

This is the second part of TomEE as code. The examples use a generic application called app with a PostgreSQL database.

The server in the POM

The TomEE Maven plugin downloads a given TomEE distribution, adds and removes libraries, copies your files over it and deploys your WAR.

<properties>
    <tomee.version>10.0.0</tomee.version>
    <postgresql.version>42.7.4</postgresql.version>
</properties>

<build>
    <plugins>
        <plugin>
            <groupId>org.apache.tomee.maven</groupId>
            <artifactId>tomee-maven-plugin</artifactId>
            <version>${tomee.version}</version>
            <configuration>
                <tomeeVersion>${tomee.version}</tomeeVersion>
                <tomeeClassifier>webprofile</tomeeClassifier>
                <context>ROOT</context>
                <libs>
                    <!-- The server manages the connection pool, so the driver belongs to the server -->
                    <lib>org.postgresql:postgresql:${postgresql.version}</lib>
                    <!-- The application brings its own JPA provider -->
                    <lib>remove:openjpa-</lib>
                </libs>
            </configuration>
        </plugin>
    </plugins>
</build>

This block holds every difference between this server and a standard TomEE. An upgrade is a one-line change to a property.

  • Pin the version. A version such as “latest” changes without a commit.
  • Take library versions from properties. When the WAR compiles against a library the server provides, such as a logging API, both declarations use the same property.
  • Check removals after each upgrade. remove:openjpa- deletes every JAR whose name starts with openjpa-. If a later TomEE renames that JAR, the removal stops matching without any error, so look at the server’s lib/ folder.

The overlay folder

The plugin copies src/main/tomee/ over the downloaded distribution. A file in src/main/tomee/conf/ goes to the server’s conf/, and the same applies to lib/ and bin/. This folder is the overlay, the only place where you edit server files by hand.

src/main/tomee/
├── conf/
│   ├── catalina.properties
│   ├── logging.properties
│   ├── server.xml
│   └── tomee.xml
└── lib/
    └── app/
        └── app.properties
  • Mirror the server’s layout, so nobody needs a mapping to find where a file goes.
  • Keep working defaults for development, so a fresh checkout runs without edits.
  • Keep secrets out. Passwords and keys come from the environment.
  • Track every file in Git. A file that Git tracks but .gitignore hides also hides the changes a reviewer should see. A setting that differs on your machine is a value, and values come from the environment.
  • Include only the files you changed. You own each file you copy in, and you must merge TomEE’s changes to it on every upgrade. Start from the copy in the same TomEE version.

Values from the environment

The overlay describes the server’s structure. Values such as the database URL, the user, the password and a data directory change per environment, so environment variables supply them.

TomEE resolves ${...} placeholders in tomee.xml:

<tomee>
    <Resource id="jdbc/appDS" type="javax.sql.DataSource">
        JdbcDriver = org.postgresql.Driver
        JdbcUrl = ${APP_DB_URL}
        UserName = ${APP_DB_USER}
        Password = ${APP_DB_PASSWORD}
        JtaManaged = true
    </Resource>
</tomee>

Tomcat resolves placeholders in server.xml too, but by default it reads only Java system properties. This line in conf/catalina.properties makes it read environment variables:

org.apache.tomcat.util.digester.PROPERTY_SOURCE=org.apache.tomcat.util.digester.EnvironmentPropertySource

The line belongs in catalina.properties, not in TomEE’s system.properties, because Tomcat reads catalina.properties before it parses server.xml, however the server starts. It then applies to the IDE and to the container, declared once.

One root for every path

The developer uses C:\dev\data\input and the container uses /app/data/input, so lists of paths tend to exist twice. Give the application one root variable and make every path relative to it:

# lib/app/app.properties
data.root=${APP_DATA_ROOT}
data.input=${data.root}/input
data.output=${data.root}/output

APP_DATA_ROOT is C:/dev/data on the developer’s machine and /app/data in the container. Java accepts forward slashes on Windows, so one variable works in both places. The application must expand placeholders when it reads the file. With MicroProfile Config, environment variables already override properties.

Running it from the IDE

mvn clean tomee:run

The plugin builds the WAR, assembles the server under target/ and starts it with the application deployed. clean deletes the previous server first, so no old deployment, leftover file or changed setting survives. Most IDEs run this command as a Maven run configuration, also with a debugger.

Local values go in a file outside Git, loaded by your shell or by the IDE’s run configuration:

# local.env (not versioned)
APP_DB_URL=jdbc:postgresql://localhost:5432/app
APP_DB_USER=app
APP_DB_PASSWORD=change-me
APP_DATA_ROOT=C:/dev/data

A new developer creates only this file, from a local.env.example in the repository that lists the variable names without secrets. When someone upgrades TomEE or edits server.xml, the next run on every machine includes the change.

Principles checked

PrincipleHow this setup passes the check
DeclarativeThe POM and the overlay describe the server, and the only instruction is mvn clean tomee:run
Versioned and reviewableEvery change is a diff, and .gitignore hides no tracked file
ReproducibleDeleting target/ and running again gives the same server
No manual stateThe build creates the server again on every run
Single sourceThe overlay holds the structure, the environment holds the values, and properties hold the versions

The sixth principle, the same server everywhere, needs a container. The third part builds one from the same description.