Turn two images into a local instance
This companion brings a local Manage application and its DB2 database into one Podman pod, managed by systemd through Quadlet. Build the Manage and DB2 images first. Add an Alpine Nginx proxy using the standalone Nginx guide, including its image build, certificate directory and port mappings.
The image internals, build files and first-start database changes are covered in the password-protected Manage build guide and DB2 build guide. This article has no password gate and does not reproduce those downloads or any environment secrets.
The arrangement
HOST POD · Manage921Demo
┌─────────────────────────────────┐
Browser │ Nginx │
127.0.0.1:9080 ──────────────────┼─► :80 ── redirect to host :9443 │
127.0.0.1:9443 ──────────────────┼─► :443 ─────────► Liberty :9443 │
│ │
SQL client │ DB2 │
127.0.0.1:50005 ─────────────────┼─► :50005 │
│ │
Java debugger │ Manage │
127.0.0.1:3333 ──────────────────┼─► :3333 JDWP │
│ │
Automation script debugger │ Manage │
127.0.0.1:4711 ──────────────────┼─► :4711 │
└─────────────────────────────────┘All three containers share the pod's network namespace. Nginx reaches Liberty at https://127.0.0.1:9443; Manage reaches DB2 at localhost:50005. Liberty's 9080 and 9443 remain internal. Host web ports 9080 and 9443 map to Nginx ports 80 and 443 respectively.
Separate host directories persist the database, recovery files, SMP, plugins, attachments, JMS state and Nginx certificates. With WSL, a Windows browser normally reaches the proxy at https://localhost:9443/maximo/ through localhost forwarding.
Prerequisites
Use a Linux environment with systemd, cgroup v2 and a Podman/Quadlet version supporting .pod files. On Windows, run these Bash commands inside the Linux environment that owns the containers. Use the same Linux user for image builds, volumes and user services.
Check that the images exist under the names used below:
podman image inspect localhost/manage:9.2.1 --format '{{.Id}}'
podman image inspect localhost/db2:11.5.6 --format '{{.Id}}'
podman image inspect localhost/manage-nginx:dev --format '{{.Id}}'If your tags differ, change Image= in the corresponding container file. Pull=never intentionally requires local images. A tag is not proof of the installed product version.
Use a fresh instance for these steps. If manual containers already own manage921, stop and deliberately migrate their definitions and storage before switching to Quadlet. Do not run two DB2 containers against the same writable database volumes.
Prepare one shared environment file
Manage and DB2 read instance.env; Nginx reads only manage_domain through its service setting below. The shared database values keep their connection settings aligned; each startup script uses the variables relevant to it.
install -d -m 0700 "$HOME/.config/manage921"
install -d -m 0755 "$HOME/.config/containers/systemd/Manage921Demo"
touch "$HOME/.config/manage921/instance.env"
chmod 0600 "$HOME/.config/manage921/instance.env"Edit instance.env:
db2_instance_name=db2inst
db2_instance_port=50005
db2_instance_uid=2000
db2_instance_gid=2000
db_platform=DB2
db_hostname=localhost
db_port=50005
db_database_name=maximo
db_username=maximo
db_password=REPLACE_WITH_DATABASE_PASSWORD
crypto_key=REPLACE_WITH_INSTANCE_CRYPTO_KEY
cryptox_key=REPLACE_WITH_INSTANCE_CRYPTOX_KEY
install_demo_data=true
build_maximo_ear=true
maximo_update_db=true
java_debug_port=3333
s3_doclinks=false
manage_domain=manage.demo.localjava_debug_port=3333 enables Java remote debugging (JDWP). Attach your IDE to 127.0.0.1:3333; the startup script configures suspend=n, so the JVM starts without waiting for a debugger. This is separate from AutoDebug's automation-script debugger. Keep the pod mapping on host loopback.
Replace all placeholders and keep db2_instance_port and db_port aligned. Both containers receive the file's values, including the encryption keys, so treat the entire file as private. Use the encryption keys corresponding to an existing database and retain them with backups. The DB2 image's fixed schema grant assumes maximo as the application database user.
The first-start options can initialise or update the database. They are not generic safe restart settings for an arbitrary shared environment. Read the protected build guides before pointing these containers at existing data.
Keep this environment's .pod, .container and .volume files together in ~/.config/containers/systemd/Manage921Demo/. Quadlet searches subdirectories recursively, so the existing systemctl --user service names stay the same. See the Quadlet unit search-path documentation.
Subdirectories organise the files; they do not namespace unit names. For another environment, use distinct Quadlet filenames, pod/container/volume names, storage paths and host ports to avoid collisions.
Define the shared pod
Save Manage921Demo.pod in ~/.config/containers/systemd/Manage921Demo/:
[Unit]
Description=Local Manage and DB2 pod
[Pod]
PodName=Manage921Demo
PublishPort=127.0.0.1:9080:80
PublishPort=127.0.0.1:9443:443
PublishPort=127.0.0.1:50005:50005
PublishPort=127.0.0.1:3333:3333
PublishPort=127.0.0.1:4711:4711Publish ports on the pod, not its member containers. The loopback binding limits browser access to the host by default. Use SSH forwarding or an appropriately secured proxy if access from another machine is required.
Prepare storage in your home directory
These are directory-backed named volumes: each has its own .volume Quadlet file, using a bind mount to one of the directories under ~/PodmanEnvironment/Manage921Demo. The container images remain in Podman's image store; their persistent data is mapped here.
~/PodmanEnvironment/Manage921Demo/
├── Manage/
│ ├── SMP/
│ ├── Plugins/
│ ├── Doclinks/
│ └── JMS/
├── DB2/
│ ├── Home/
│ ├── Backups/
│ └── ArchiveLogs/
└── Nginx/
└── Certs/For a new instance, create the directories as the Linux user running Podman:
instance_dir="$HOME/PodmanEnvironment/Manage921Demo"
install -d -m 0700 "$instance_dir" \
"$instance_dir/Manage/SMP" "$instance_dir/Manage/Plugins" \
"$instance_dir/Manage/Doclinks" "$instance_dir/Manage/JMS" \
"$instance_dir/DB2/Home" "$instance_dir/DB2/Backups" \
"$instance_dir/DB2/ArchiveLogs" \
"$instance_dir/Nginx/Certs"Let Podman populate the Manage volumes
The four Manage volume definitions use Copy=true. On their first use with empty backing directories, Podman copies the image contents at each mount point into the corresponding volume: SMP, plugins, doclinks and JMS. No helper container or manual copy is needed for a fresh Quadlet instance. See the Quadlet Copy reference.
This is initial population, not ongoing synchronisation. Existing data is retained; changing the image does not refresh an already-initialised SMP volume. Start with empty Manage directories only when creating a new instance, and verify Manage/SMP/maximo and the expected plugin files after initial startup.
The DB2 volumes retain Copy=false: the DB2 startup script creates its instance in the fresh DB2/Home directory and assigns ownership of its backup/archive directories. Keep the configured instance IDs stable.
Existing instances need a data migration
Back up and stop Manage and DB2 before switching their mounts. Copy the existing persistent contents, not a fresh image's contents, into the new directories while preserving ownership and permissions. A stopped container can supply its mounted data using podman cp; preserve container UID/GID ownership through the rootless user namespace when extracting it. Copy all seven paths, including DB2's /home, backups and archive logs, before changing the definitions.
Retain the old volumes until database recovery, application startup and backups have been verified. Do not start with an empty DB2 directory unless you intend to create a new database. If a named volume already exists, editing its .volume file does not change its stored driver options. Use new volume names while migrating, or deliberately recreate only its metadata after backing up and verifying the data. Confirm the device paths with podman volume inspect before starting the containers.
Define each volume
Keep one Quadlet file per persistent directory. Device= points to the prepared host folder; %h resolves to the service user's home directory. Type=none and Options=bind make the local driver use that folder. Copy=true populates fresh Manage volumes from the image; DB2 uses Copy=false because its startup script initialises the database directories.
Save Manage921Demo-DB2-Home.volume in ~/.config/containers/systemd/Manage921Demo/:
[Volume]
VolumeName=Manage921Demo-DB2-Home
Driver=local
Type=none
Device=%h/PodmanEnvironment/Manage921Demo/DB2/Home
Options=bind
Copy=falseSave Manage921Demo-DB2-Backups.volume in ~/.config/containers/systemd/Manage921Demo/:
[Volume]
VolumeName=Manage921Demo-DB2-Backups
Driver=local
Type=none
Device=%h/PodmanEnvironment/Manage921Demo/DB2/Backups
Options=bind
Copy=falseSave Manage921Demo-DB2-ArchiveLogs.volume in ~/.config/containers/systemd/Manage921Demo/:
[Volume]
VolumeName=Manage921Demo-DB2-ArchiveLogs
Driver=local
Type=none
Device=%h/PodmanEnvironment/Manage921Demo/DB2/ArchiveLogs
Options=bind
Copy=falseSave Manage921Demo-SMP.volume in ~/.config/containers/systemd/Manage921Demo/:
[Volume]
VolumeName=Manage921Demo-SMP
Driver=local
Type=none
Device=%h/PodmanEnvironment/Manage921Demo/Manage/SMP
Options=bind
Copy=trueSave Manage921Demo-Plugins.volume in ~/.config/containers/systemd/Manage921Demo/:
[Volume]
VolumeName=Manage921Demo-Plugins
Driver=local
Type=none
Device=%h/PodmanEnvironment/Manage921Demo/Manage/Plugins
Options=bind
Copy=trueSave Manage921Demo-Doclinks.volume in ~/.config/containers/systemd/Manage921Demo/:
[Volume]
VolumeName=Manage921Demo-Doclinks
Driver=local
Type=none
Device=%h/PodmanEnvironment/Manage921Demo/Manage/Doclinks
Options=bind
Copy=trueSave Manage921Demo-JMS.volume in ~/.config/containers/systemd/Manage921Demo/:
[Volume]
VolumeName=Manage921Demo-JMS
Driver=local
Type=none
Device=%h/PodmanEnvironment/Manage921Demo/Manage/JMS
Options=bind
Copy=trueSave Manage921Demo-Nginx-Certs.volume in ~/.config/containers/systemd/Manage921Demo/:
[Volume]
VolumeName=Manage921Demo-Nginx-Certs
Driver=local
Type=none
Device=%h/PodmanEnvironment/Manage921Demo/Nginx/Certs
Options=bind
Copy=falseNginx generates its certificate and private key in this writable volume at startup. Copy=false leaves initialisation to the entrypoint. Existing certificates in the backing directory are retained. Keep this directory private and separate for each instance.
The container files reference these .volume filenames. Quadlet then adds dependencies on their generated volume services. The host directories must exist before startup.
Define DB2
Save Manage921Demo-DB2.container in ~/.config/containers/systemd/Manage921Demo/:
[Unit]
Description=DB2 for local Manage
[Container]
Image=localhost/db2:11.5.6
Pull=never
ContainerName=Manage921Demo-DB2
Pod=Manage921Demo.pod
EnvironmentFile=%h/.config/manage921/instance.env
AddCapability=IPC_LOCK IPC_OWNER
Volume=Manage921Demo-DB2-Home.volume:/home:Z,suid
Volume=Manage921Demo-DB2-Backups.volume:/db2/backups:Z
Volume=Manage921Demo-DB2-ArchiveLogs.volume:/db2/arclogs:Z
HealthCmd=su - db2inst -c "db2 connect to maximo"
HealthInterval=30s
HealthTimeout=20s
HealthStartPeriod=300s
HealthRetries=5
[Service]
TimeoutStartSec=900The capabilities reflect the DB2 build guide. Their effectiveness under rootless Podman and the host's memory-lock limits must be verified. Do not assume adding capabilities inside a user namespace grants unrestricted host privileges.
The DB2 home mount explicitly uses suid. Podman volumes otherwise default to nosuid, which can prevent DB2 instance programs from obtaining the required privileges. This was confirmed by SQL1641N in the DB2 diagnostic log when /home was mounted with nosuid. Keep the named volume and permit setuid on this mount; no privileged container is needed. The host backing filesystem must also permit setuid.
The health command uses the configured instance/database names. Update it if those names change. No restart-on-unhealthy policy is enabled during bootstrap, so a failed check remains available for investigation.
Define Manage
Save Manage921Demo.container in ~/.config/containers/systemd/Manage921Demo/:
[Unit]
Description=Local Maximo Manage
Requires=Manage921Demo-DB2.service
After=Manage921Demo-DB2.service
[Container]
Image=localhost/manage:9.2.1
Pull=never
ContainerName=Manage921Demo
Pod=Manage921Demo.pod
EnvironmentFile=%h/.config/manage921/instance.env
Volume=Manage921Demo-SMP.volume:/opt/IBM/SMP:Z
Volume=Manage921Demo-Plugins.volume:/opt/IBM/MaximoPlugins:Z
Volume=Manage921Demo-Doclinks.volume:/doclinks:Z
Volume=Manage921Demo-JMS.volume:/jmsstore:Z
HealthCmd=curl -fsS http://localhost:9080/maximo/api/bamessages/ping
HealthInterval=30s
[Service]
TimeoutStartSec=900
ExecStartPre=/bin/sh -c 'until podman healthcheck run Manage921Demo-DB2; do sleep 10; done'After= orders startup. The additional readiness loop waits for DB2's health check before the Manage container starts, bounded by the service timeout. It does not verify the application's database password or required tablespaces.
The pod and .volume references create dependencies on their generated units. The backing directories must already exist. :Z applies a private SELinux label to each dedicated directory; never relabel your entire home directory. See the Podman Quadlet reference for generation and dependency behaviour.
Define Nginx
Save Manage921Demo-Nginx.container in ~/.config/containers/systemd/Manage921Demo/. Build the image using the Nginx guide and prepare the certificate directory and volume defined above.
[Unit]
Description=Local Manage HTTPS proxy
[Container]
Image=localhost/manage-nginx:dev
Pull=never
ContainerName=Manage921Demo-Nginx
Pod=Manage921Demo.pod
Environment=manage_domain
Volume=Manage921Demo-Nginx-Certs.volume:/etc/nginx/certs:Z
HealthCmd=wget -q --spider --no-check-certificate https://127.0.0.1/nginx-health
HealthInterval=30s
[Service]
EnvironmentFile=%h/.config/manage921/instance.env
TimeoutStartSec=120Nginx depends on the pod and certificate volume through its Quadlet references. It has no Requires= or After= dependency on Manage, so it can serve requests while the application starts. The image supplied by the Nginx article displays a loading page and polls the Manage login endpoint until the application responds. Its service reads manage_domain from instance.env and passes only that variable into the proxy container.
After generating and reloading the units, you can start the proxy before DB2 and Manage:
systemctl --user start Manage921Demo-Nginx.serviceHost ports 9080 and 9443 must be free. These mappings use unprivileged host ports with the usual Linux threshold of 1024, so no low-port sysctl change is needed. Nginx listens on 80 and 443 inside the pod.
Start DB2 and verify it first
Load and inspect the generated services:
quadlet --user ~/.config/systemd/user
systemctl --user daemon-reload
systemctl --user cat Manage921Demo-DB2.service
systemctl --user cat Manage921Demo.service
systemctl --user start Manage921Demo-DB2.service
podman logs -f Manage921Demo-DB2Allow initialisation to finish, then check connectivity:
podman healthcheck run Manage921Demo-DB2
podman exec Manage921Demo-DB2 su - db2inst -c 'db2 connect to maximo && db2 list tablespaces show detail'
podman exec -it Manage921Demo-DB2 su - db2instIn the interactive session, test the application user:
db2 connect to maximo user maximo
db2 connect reset
exitEnter its password at the prompt. Confirm the expected schema and tablespaces, review bootstrap errors and verify that the initial backup exists as described in the DB2 companion. A successful instance-owner health check is not sufficient evidence that all first-start setup succeeded.
Start Manage
systemctl --user start Manage921Demo.service
podman logs -f Manage921DemoFollow database installation/update and EAR-building progress before expecting Liberty to serve the application. Once startup completes:
podman healthcheck run Manage921Demo
podman exec Manage921Demo curl --fail http://localhost:9080/maximo/api/bamessages/ping
systemctl --user status Manage921Demo-DB2.service Manage921Demo.serviceThen start the browser-facing proxy:
systemctl --user start Manage921Demo-Nginx.service
podman exec Manage921Demo-Nginx nginx -t
podman healthcheck run Manage921Demo-Nginx
curl -I http://localhost:9080/maximo/
curl -k -I https://localhost:9443/maximo/Open https://localhost:9443/maximo/, check authentication and record the actual application and DB2 versions. Follow the Nginx guide for certificate trust on the browser's operating system. The -k flag is for diagnostics; repeat without it after configuring trust. Verify redirects stay on host port 9443 and check attachments and application links. Proxy health alone does not prove Manage readiness.
Connect a database client from the host
With the pod and DB2 running, configure your DB2 client with:
| Setting | Value |
|---|---|
| Host | 127.0.0.1 |
| Port | 50005 |
| Database | maximo |
| User | maximo |
| Password | The db_password value in instance.env |
Manage continues to use localhost:50005 inside the pod. The client uses the port published on the host. Both reach the same database.
The loopback binding permits host-local access. For a client on another machine, use an SSH tunnel to the host's port 50005 or deliberately configure a restricted network binding and firewall rules. This example does not expose DB2 on every interface or configure database TLS.
If you already created the pod without this port mapping, editing the file and reloading systemd is insufficient to change the running pod's network configuration. Stop the application and database, recreate the pod through its Quadlet service, then start DB2 and verify it before starting Manage. Preserve all bind-mounted directories.
Switch an existing pod to Nginx
Build Nginx and prepare certificates first. Save its Quadlet and change the pod mappings from 9080:9080 and 9443:9443 to 9080:80 and 9443:443. During a planned outage:
systemctl --user stop Manage921Demo-pod.service
quadlet --user ~/.config/systemd/user
systemctl --user daemon-reload
systemctl --user start Manage921Demo-DB2.service
podman healthcheck run Manage921Demo-DB2Wait for DB2's health check to pass, then:
systemctl --user start Manage921Demo.service
systemctl --user start Manage921Demo-Nginx.service
podman port Manage921Demo-infraConfirm web mappings are 9080 -> 80 and 9443 -> 443 and repeat the application and proxy checks above. Preserve all volumes and host data directories; changing ports does not require a database migration. To roll back, stop the pod, restore the previous mappings, reload the definitions and start DB2 and Manage without starting Nginx.
Everyday lifecycle
Stop the whole environment through its pod service. Quadlet links the member container services to the pod, so they stop with it; separate container stop commands are unnecessary:
systemctl --user stop Manage921Demo-pod.serviceFor definition changes, run quadlet --user ~/.config/systemd/user followed by systemctl --user daemon-reload before starting again. Start DB2, verify it, then start Manage and Nginx. Restarting a container preserves its host directories but does not automatically upgrade the database or refresh persisted SMP files.
If you update credentials, update the shared environment file and the actual database account through a deliberate procedure. Changing an environment variable alone is not proof that an existing installation has adopted the new value.
Optional startup with the user manager
After successful initial verification, add this section to both Manage921Demo.container and Manage921Demo-Nginx.container:
[Install]
WantedBy=default.targetReload the units. Nginx and Manage start independently; Manage's dependency starts DB2. Quadlet applies the install section during generation; do not run systemctl enable on the generated service. To keep the user manager running after logout:
loginctl enable-linger "$USER"The host may require administrator authorisation. WSL must itself be running for its services to run.
Troubleshooting
| Symptom | Check |
|---|---|
| Service not found | Quadlet support, file extensions and generator output |
| Image not found | Local image tag and the Linux user owning the image store |
| Container name already used | Existing manual containers or earlier services |
| Manage readiness wait expires | DB2 bootstrap logs and its actual health check |
| DB2 healthy but Manage cannot connect | Application user's credentials, schema, port and grants |
| Container active but web page unavailable | Manage build/startup logs and application health |
| New image still appears to run old code | Persisted SMP and plugin directory contents |
For a missing generated unit, inspect the generator at its installed path, for example:
/usr/lib/systemd/system-generators/podman-system-generator --user --dryrunKeep database backups outside the instance's failure domain and test restoration separately. Do not delete the host data directories to resolve a routine service failure. The goal is a reproducible local instance whose lifecycle is clear, with the build details kept in the protected companions.