← Oracle ACE articles

ACE-U field notes

Minimal Java container on OCI with TLS to DB

A small, locked-down Java service on an Ampere A1 instance, talking to Oracle Database over TLS.

Shubham Anandani··3 min read

Problem

I wanted the smallest useful baseline: a Java service in a container, on a cheap OCI instance, connecting to an Oracle database over TLS, with no credentials baked into the image. It should be small enough to read in one sitting and safe enough to build on.

Options and trade-offs

  • Ampere A1 with Podman: efficient and economical, and you need arm64 images.
  • Docker on an x86 shape: the widest image support, at a higher cost as you scale.
  • TLS only versus mTLS with a wallet: TLS means less to handle, and mTLS gives stronger assurance.

My recommendation

For a small service I use an Ampere A1 instance with Podman, TLS first, and mTLS when policy requires it. The image holds only the application. Credentials and any wallet are injected when the container starts. UCP stays at conservative sizes.

Worked example

Install the container tooling on Oracle Linux 8 and build on the same architecture you will run on. Building on the A1 instance avoids cross-architecture surprises.

sudo dnf module install -y container-tools:ol8
sudo dnf install -y podman-docker git

A Dockerfile that runs as a non-root user. The Temurin JRE image is published for arm64 as well as x86.

FROM eclipse-temurin:17-jre
RUN useradd --system --no-create-home appuser
WORKDIR /app
COPY target/app.jar /app/app.jar
USER appuser
CMD ["java", "-jar", "/app/app.jar"]

Run it with the password supplied as a Podman secret, and with a read-only filesystem and no extra capabilities:

printf '%s' "$DB_PASSWORD" | podman secret create db_password -
 
podman run -d --name app -p 8080:8080 \
  --read-only --tmpfs /tmp --cap-drop=ALL \
  --env JDBC_URL='<tcps_jdbc_url>' --env DB_USER='<db_user>' \
  --secret db_password,type=env,target=DB_PASSWORD \
  <region-key>.ocir.io/<tenancy-namespace>/myteam/myapp:1.4.2

In the application, build the pool from environment values and keep the sizes modest:

PoolDataSource pds = PoolDataSourceFactory.getPoolDataSource();
pds.setConnectionFactoryClassName("oracle.jdbc.pool.OracleDataSource");
pds.setURL(System.getenv("JDBC_URL"));          // tcps:// URL
pds.setUser(System.getenv("DB_USER"));
pds.setPassword(System.getenv("DB_PASSWORD"));
pds.setMinPoolSize(2);
pds.setMaxPoolSize(10);
pds.setValidateConnectionOnBorrow(true);

For mTLS, mount the wallet directory read-only at run time, set TNS_ADMIN to it, and let ojdbc.properties supply the wallet location. Do not copy it into the image. Remember to open the application port in both the instance firewall and the network security group.

Checklist

  1. Confirm every dependency and base image supports arm64.
  2. Run as a non-root user with a read-only filesystem.
  3. Inject database credentials at start-up from a secret store.
  4. Start with TLS, and move to mTLS when required.
  5. Size the pool for the database, not for the instance.
  6. Test a bad certificate and a database restart before cutover.

Where this doesn't apply

Large frameworks with big memory footprints may be a poor fit for a small instance. Pick a shape for the workload instead of forcing it onto the smallest one.

One honest limitation

TLS and wallet problems tend to show up as vague connection errors. I keep a tiny test program that only opens a connection, so I can separate driver and certificate problems from application bugs.

Sources

  • OCI Documentation: Deploying Java Applications on Ampere A1
  • Podman Documentation: podman secret and podman run
  • Oracle JDBC Developer's Guide: Client-Side Security Features