Deployment in a Development Environment - hmislk/hmis GitHub Wiki
This page walks through installing a working local copy of HMIS โ clone, build, database, application server, deploy, first login โ end to end. It is written to be followed literally, including by an AI coding agent with shell access: every step has an exact command and a way to check it worked before moving on.
If you only need to point an already-running local Payara at the app (JNDI names already created), skip to Configure persistence.xml.
| Component | Version used by this project | Notes |
|---|---|---|
| JDK | 11 (Temurin/Adoptium recommended) | The build fails/misbehaves on JDK 17+. pom.xml compiles with --release 11. |
| Build tool | Maven 3.6+ | Produces a WAR via mvn package. |
| Application server | Payara 5 (5.2022.5 or newer 5.x) | GlassFish 5-compatible; not Payara 6/Jakarta EE 9+. |
| Database | MySQL 8.0.x or MariaDB 10.x | JDBC driver bundled in the build is com.mysql:mysql-connector-j:8.0.33. |
| OS | Linux (Ubuntu) or Windows | Both work for local dev; commands below are given for both where they differ. |
The app is a single WAR (Maven artifactId rh, e.g. rh-3.0.0.war) with a fixed context root of /rh (src/main/webapp/WEB-INF/glassfish-web.xml), so once deployed it's reachable at http://localhost:8080/rh/.
It uses two separate JPA persistence units, each backed by its own JNDI datasource, both of which must exist in Payara before the app will deploy successfully:
-
hmisPUโ the main application database (JNDI name configured insrc/main/resources/META-INF/persistence.xml, currentlyjdbc/coopin this checkout โ see step 6, this is a per-developer local value, never a fixed name) -
hmisAuditPUโ the audit-log database (jdbc/ruhunuAuditin this checkout)
For local dev these can point at two databases on the same MySQL instance (simplest), or even the same database โ they don't have to be physically separate.
Ubuntu:
sudo apt-get update
sudo apt-get install -y git openjdk-11-jdk maven mysql-server
java -version # must report 11.x
mvn -version # confirm it picked up JDK 11, not a newer default JDKWindows: install Git, a JDK 11 build (e.g. Eclipse Adoptium 11), Maven, and MySQL or MariaDB. Make sure JAVA_HOME points at the JDK 11 install and mvn -version reports it.
git clone https://github.com/hmislk/hmis.git
cd hmis
git checkout development # the active integration branch โ see CONTRIBUTING.md
mvn clean package -DskipTestsVerify: the build ends with BUILD SUCCESS and prints the WAR path, e.g.:
[INFO] Building war: /path/to/hmis/target/rh-3.0.0.war
If the version number differs, just substitute it in later steps โ the important part is the file lives at target/rh-*.war.
Download and unzip Payara Community 5.2022.5 (or any Payara 5.x) to a folder โ call it <PAYARA_HOME> below.
Point Payara at your JDK 11 install:
Linux (<PAYARA_HOME>/glassfish/config/asenv.conf):
AS_JAVA="/usr/lib/jvm/java-11-openjdk-amd64"
Windows (<PAYARA_HOME>\glassfish\config\asenv.bat):
set AS_JAVA=C:\path\to\jdk-11
Make sure nothing else is using port 8080, then start the domain:
<PAYARA_HOME>/bin/asadmin start-domain domain1Verify: curl -I http://localhost:4848 returns a response (admin console is up), and <PAYARA_HOME>/bin/asadmin list-domains shows domain1 running.
Create an empty database and a user for it. This example uses one database for both persistence units โ see the table in the intro if you'd rather separate them.
CREATE DATABASE hmis CHARACTER SET utf8mb4;
CREATE USER 'hmis'@'localhost' IDENTIFIED BY 'hmis';
GRANT ALL PRIVILEGES ON hmis.* TO 'hmis'@'localhost';
FLUSH PRIVILEGES;Copy the MySQL JDBC driver into Payara's classpath (must match the version the app was built against โ currently 8.0.33):
# Download from Maven Central:
# https://repo1.maven.org/maven2/com/mysql/mysql-connector-j/8.0.33/mysql-connector-j-8.0.33.jar
cp mysql-connector-j-8.0.33.jar <PAYARA_HOME>/glassfish/lib/
<PAYARA_HOME>/bin/asadmin stop-domain domain1
<PAYARA_HOME>/bin/asadmin start-domain domain1Create one pool/resource pair per persistence unit. The JNDI resource names must exactly match whatever is in <jta-data-source> in persistence.xml (step 6) โ pick names now and use the same ones there.
ASADMIN=<PAYARA_HOME>/bin/asadmin
# Main application datasource
$ASADMIN create-jdbc-connection-pool \
--datasourceclassname com.mysql.cj.jdbc.MysqlDataSource \
--restype javax.sql.DataSource \
--property "user=hmis:password=hmis:databaseName=hmis:serverName=localhost:port=3306:useSSL=false:allowPublicKeyRetrieval=true:rewriteBatchedStatements=true" \
hmisPool
$ASADMIN create-jdbc-resource --connectionpoolid hmisPool jdbc/coop
# Audit datasource (can point at the same database)
$ASADMIN create-jdbc-connection-pool \
--datasourceclassname com.mysql.cj.jdbc.MysqlDataSource \
--restype javax.sql.DataSource \
--property "user=hmis:password=hmis:databaseName=hmis:serverName=localhost:port=3306:useSSL=false:allowPublicKeyRetrieval=true" \
hmisAuditPool
$ASADMIN create-jdbc-resource --connectionpoolid hmisAuditPool jdbc/ruhunuAuditrewriteBatchedStatements=true on the main pool matters: persistence.xml turns on EclipseLink JDBC batch-writing, and MySQL Connector/J silently ignores batching without this property.
Verify each pool can actually reach the database:
$ASADMIN ping-connection-pool hmisPool
$ASADMIN ping-connection-pool hmisAuditPoolBoth must print Command ping-connection-pool executed successfully. If a ping fails, re-check the driver jar (step 4) and the pool's user/password/databaseName properties before continuing โ nothing downstream will work until this passes.
Open src/main/resources/META-INF/persistence.xml and check the two <jta-data-source> values:
grep -A1 jta-data-source src/main/resources/META-INF/persistence.xml- If they already match the JNDI names you created in step 5 (e.g.
jdbc/coop,jdbc/ruhunuAudit), you're done. - If they show placeholders (
${JDBC_DATASOURCE},${JDBC_AUDIT_DATASOURCE}) โ the form this file must be in before everygit pushโ edit them locally to your JNDI names from step 5.
๐จ Never commit this local edit. These names are per-developer/per-environment; committing hardcoded names breaks CI/CD deployments for everyone else. Keep the change unstaged, and restore the placeholders (or git checkout the file) before pushing. See developer_docs/deployment/persistence-verification.md in the main repo for the full rule.
A freshly created empty database has no tables yet, and the app does not create them automatically on startup โ schema generation has to be turned on explicitly, once.
Add these two properties inside both <persistence-unit> blocks in persistence.xml (inside their <properties> section):
<property name="eclipselink.ddl-generation" value="create-or-extend-tables"/>
<property name="eclipselink.ddl-generation.output-mode" value="database"/>Deploy the WAR once (step 9 below covers the full deploy command). EclipseLink will create all ~190 tables directly in the database on that first deployment. Watch the server log for errors:
tail -f <PAYARA_HOME>/glassfish/domains/domain1/logs/server.logVerify: SHOW TABLES; in the hmis database now lists a large number of tables (e.g. WEBUSER, INSTITUTION, DEPARTMENT, BILL, ...).
Once the schema exists, remove the two properties again (or comment them out) and redeploy. Leaving create-or-extend-tables on permanently is a supported local-dev pattern for picking up new columns as entities change, but keep it deliberate โ it's one of the settings step 6's warning applies to (never push it enabled).
The application has no self-service "create first institution/user" wizard โ an empty, freshly-created schema has no INSTITUTION, DEPARTMENT, or WEBUSER rows, and there is no in-app bootstrap flow that creates them. You need at least one row in each of those tables (plus role/privilege rows) before you can log in at all.
Two practical ways to get there:
-
Restore a reference database dump (recommended). Ask a team member for a sanitized snapshot of an existing HMIS database โ it already has the schema, base reference data (categories, fee types, etc.), and at least one super-admin
WEBUSER. Restore it instead of creating an empty schema in step 7.src/main/webapp/resources/sql/new_installation.sqlcan then be run against the restored database to wipe customer-specific transactional data (bills, patients, staff, stock, etc.) while keeping the schema, reference data, and the admin account โ useful for turning a copy of a live database into a clean local sandbox. Read the script before running it: it is destructive by design and keeps only specific hardcodedWEBUSER/DEPARTMENT/INSTITUTIONIDs. -
Seed manually. If no dump is available, insert a minimal
INSTITUTION,DEPARTMENT, andWEBUSER(with the appropriate password hash and privilege rows) directly via SQL. There's no documented canonical seed script for this in the repo today โ treat it as exploratory, and cross-check theWebUser,Institution, andDepartmententity classes undersrc/main/java/com/divudi/core/entity/for required fields before inserting.
<PAYARA_HOME>/bin/asadmin deploy target/rh-3.0.0.war(substitute the actual filename from step 2 if the version differs)
Verify:
<PAYARA_HOME>/bin/asadmin list-applications
# should list: rh <ejb, web>
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/rh/
# 302 (redirect to login) means the app deployed and started correctlyOpen http://localhost:8080/rh/ in a browser and log in with the admin account from step 8.
mvn clean package -DskipTests
<PAYARA_HOME>/bin/asadmin redeploy target/rh-3.0.0.warping-connection-pool fails / app won't deploy, mentions ClassNotFoundException: com.mysql.cj.jdbc.MysqlDataSource
The driver jar isn't on Payara's classpath. Re-check step 4 โ the jar must be in <PAYARA_HOME>/glassfish/lib/ and the domain restarted afterward.
Deploy fails referencing jdbc/coop (or whatever name you used) not found
The JNDI resource name in persistence.xml doesn't match what you created in step 5. Re-run grep -A1 jta-data-source src/main/resources/META-INF/persistence.xml and asadmin list-jdbc-resources and make them match exactly.
curl http://localhost:8080/rh/ returns 404 after a successful-looking deploy
Usually means Payara did deploy but the app failed to fully initialize โ check <PAYARA_HOME>/glassfish/domains/domain1/logs/server.log for the actual exception, most often a missing table (schema not created โ step 7) or a datasource that pings but points at the wrong database.
Build succeeds with JDK 17+ installed but the app misbehaves at runtime, or Maven picks the wrong JDK
This project targets JDK 11. Confirm JAVA_HOME and mvn -version both point at a JDK 11 install, not whatever newer JDK might also be on the machine.
Table names came from a dump made on Linux and queries fail on a case-sensitive/insensitive mismatch
MySQL table-name case sensitivity differs between Linux (case-sensitive by default) and Windows/macOS (usually not). See "Cross-deployment case sensitivity" in developer_docs/database/migration-development-guide.md in the main repo if you hit this after restoring a dump from a different OS.
Never run Payara/asadmin as root on a Linux dev box that's also used for anything shared โ it leaves root-owned files behind that block subsequent deploys. Not usually relevant on a personal dev machine, but avoid sudo asadmin ... out of habit.
git clone https://github.com/hmislk/hmis.git && cd hmis && git checkout development
mvn clean package -DskipTests
<PAYARA_HOME>/bin/asadmin start-domain domain1
mysql -u root -e "CREATE DATABASE hmis CHARACTER SET utf8mb4; \
CREATE USER 'hmis'@'localhost' IDENTIFIED BY 'hmis'; \
GRANT ALL PRIVILEGES ON hmis.* TO 'hmis'@'localhost'; FLUSH PRIVILEGES;"
cp mysql-connector-j-8.0.33.jar <PAYARA_HOME>/glassfish/lib/
<PAYARA_HOME>/bin/asadmin stop-domain domain1 && <PAYARA_HOME>/bin/asadmin start-domain domain1
# create hmisPool/jdbc-coop and hmisAuditPool/jdbc-ruhunuAudit โ see step 5
# edit persistence.xml jta-data-source values to match โ see step 6 (never commit)
# add ddl-generation properties, deploy once to create schema, then remove them โ see step 7
# get an admin login (dump restore or manual seed) โ see step 8
<PAYARA_HOME>/bin/asadmin deploy target/rh-3.0.0.war
curl -s -o /dev/null -w "%{http_code}\n" http://localhost:8080/rh/ # expect 302