Quick Start & Installation Guide¶
This guide covers the deployment, configuration, and execution of the Liferay AI Commerce Accelerator (AICA).
๐ญ The Sales Engineering Demo¶
AICA is designed specifically as a Sales Engineering (SE) demonstration tool. To support diverse demonstration requirements, it is built to be turnkey and highly portable.
If you are an SE looking to run the AICA demo immediately without compiling code, the Liferay Docker Manager (LDM) remote import pipeline is the fastest route.
1. Install LDM¶
If you do not have LDM installed, install the standalone binary:
# macOS (Apple Silicon)
sudo curl -L https://github.com/peterrichards-lr/liferay-docker-manager/releases/latest/download/ldm-macos-arm64 -o /usr/local/bin/ldm && sudo chmod +x /usr/local/bin/ldm
(For Windows, Linux, or Intel Macs, refer to the Official LDM Repository).
What has actually been exercised. AICA's automated verification runs on Linux (
ubuntu-latest), and the release package is built there. Interactive verification is done on macOS (Apple Silicon). No part of this path has been exercised on Windows or on an Intel Mac, andldm quickstartitself is not yet covered by our CI on any platform โ our suite exercisesldm importandldm runagainst a checked-out tree, which is a different entry point from the one above.Nothing here is known to be broken. If you are on Windows or an Intel Mac you are the first to walk it, so please open an issue with what you hit rather than assuming it is your setup. See #1292.
2. Import & Launch AICA¶
Run the following command to automatically download the latest release package and launch the DXP container stack:
LDM will boot the database, Liferay, and the Microservice. Once booted, the Site Initializer automatically builds your demo storefront.
The release package ships the client extensions, OSGi modules, and site initializer โ not a database dump. AICA installs against vanilla DXP and every component creates what it needs on setup, so the package stays around 12 MB rather than 1 GB and never carries stale pre-seeded data. Populate the catalog yourself once the stack is up, which takes about two minutes:
Or use the AI Data Generator screen in Liferay, described in Features & Capabilities.
3. Give Docker enough memory¶
A database-free package means the first boot creates the environment rather than restoring one: schema creation, client-extension deployment, the whole site initializer run and indexing, concurrently and at peak. LDM sizes the Liferay heap from the memory Docker reports โ -Xms2048m -Xmx3072m with a 512 MB metaspace at or below 8 GB, half the reported memory above it โ so a smaller allocation does not buy a proportionally smaller stack.
On a Docker allocation of 8 GB, that first boot has been seen killed by the kernel out-of-memory reaper, which surfaces only as Timed out waiting for Liferay to become healthy (#563). Allocate 12 GB or more where the machine allows it. Where it does not, cap the heap rather than the container: jvm_heap_max in ~/.ldmrc applies to every project, and ldm run --jvm-args applies to one.
๐ ๏ธ Developer Setup (Manual Build)¶
If you are contributing to AICA or want to run it from source, follow these steps.
Prerequisites¶
- Node.js: v22+ (LTS recommended)
- Liferay DXP/Portal: 7.4+
- Yarn: 1.22+
- Liferay Blade CLI
Required Feature Flag: To support automated scaffolding of site pages and template linking, you must enable LPD-35443 (Page Management API) in Liferay. (Note: If using LDM, this is handled automatically).
1. Workspace Setup¶
Run from the root directory to install all dependencies:
2. Full Deployment to Liferay¶
To perform a full, clean deployment of all client extensions (including the ai-commerce-accelerator-batch extension required for data definitions):
3. Subsystem Development¶
For active development, you can run the microservice and frontend independently.
๐ Microservice:
The service will start on http://localhost:3001.
๐ฅ๏ธ Frontend:
The UI will be available at http://localhost:5173.
๐งช Testing & Verification¶
Automated LDM E2E Verification (Recommended)¶
To verify the entire stack (Microservice, Frontend, and Liferay Integration) in a clean environment, use the provided orchestrator:
Unit Tests & Quality Gates¶
You can run tests for all components from the root:
The project implements an enforced testing strategy. The deploy task is dependent on testAllCX, meaning client extensions will only be deployed if all tests pass. We target a minimum of 45% statement coverage across the SDK and microservice codebases (vitest.config.mjs).
Packaging (.ldmp)¶
To package the AICA suite into a single .ldmp bundle for distribution, use LDM's own packaging command directly (the standalone scripts/package-ldmp.sh wrapper was removed โ see .github/workflows/package-ldmp.yml for the CI invocation):
# Stop the stack, then empty Liferay's data and state volumes
ldm stop -p aica-e2e -y
docker run --rm -v aica-e2e-data:/v alpine sh -c 'rm -rf /v/..?* /v/.[!.]* /v/* 2>/dev/null; true'
docker run --rm -v aica-e2e-state:/v alpine sh -c 'rm -rf /v/..?* /v/.[!.]* /v/* 2>/dev/null; true'
rm -rf aica-e2e/data aica-e2e/osgi/state
# Snapshot, then package that snapshot
ldm snapshot -p aica-e2e -y
ldm package aica-e2e --use-latest --repo <your-github-repository> -y
This outputs a .ldmp bundle and a SHA-256 checksum file, around 2.3 MB. See .github/workflows/package-ldmp.yml for the exact release invocation.
Several things about this sequence are not obvious, and each cost a failed release to learn:
- The database is excluded by stopping the stack, not by a flag. LDM decides whether to bundle a dump purely by probing whether the database container is running.
ldm snapshot --files-onlyis accepted by the CLI but read by no code path, so it has no effect. - Emptying the volumes matters as much as stopping. Snapshotting dehydrates the
dataandstateDocker volumes back onto the host and archives them, which keeps the bundle near 1 GB even with no database. Empty them through a throwaway mount rather thandocker volume rm, which refuses while a stopped container still references them. --host-nameand--sslonldm packagedo nothing.cmd_packagetakes no such parameters. The published values come from the snapshot'smeta, which the release workflow rewrites directly. Left alone, the package inherits whatever the build environment used.- The workflow strips pinned client-extension routes. The snapshot captures the Liferay service's environment into
custom_env, which is propagated to consumers on import โ includingLIFERAY_ROUTES_CLIENT_EXTENSION_..._MICROSERVICEpointing at the build environment's own container hostname, which resolves nowhere else. Dropping it lets Liferay auto-register the route for the consumer's project when the client extension is deployed, which is the same reasonserviceAddressis never hand-edited inclient-extension.yaml. - The workflow strips the build environment's search topology. The harness boots with
--sidecar, so LDM writes...ElasticsearchConfiguration.operationMode=EMBEDDEDintofiles/portal-ext.propertiesand captures the matchingLIFERAY_ELASTICSEARCH_*variables intocustom_env. Both reach the consumer: the properties file is restored as layer 2 of LDM's properties cascade, wheremodule.framework.properties.*outranks the OSGi config LDM writes for shared search, andcustom_envis appended to the Liferay service after LDM's own search settings, so a repeated key is the one Docker Compose resolves to. A consumer whose project resolved to shared search would therefore start a second Elasticsearch inside the Liferay container โ-Xms1g -Xmx2g -XX:+AlwaysPreTouchby default on2026.q1.7-lts, on top of the heap LDM sized for the whole machine.scripts/sanitize-ldm-package.cjsremoves both, and verification fails the release if either comes back. - The workflow keeps the build's own files out of the package.
ldm import .copies this repository'sscripts/into the project, and Liferay runs every script in/mnt/liferay/scriptsat boot, so a consumer's Liferay would run our tooling.routes/default/holds what the build's Liferay wrote: OAuth client credentials that are dead on any other install, and a DXP tree naming the build's host. The workflow empties both directories before snapshotting, keeping the directories themselves, and verification fails the release if anything comes back. It also strips anyLIFERAY_JDBC_PERIOD_*key fromcustom_env, because with one present LDM writes nojdbc.*properties, and the packagedjdbc.default.url, which names the build's database container, would survive. - The workflow also declares
client_extensionsitself. LDM derivesincludes_client_extensionsfromcx/,deploy/and the build directory, butclient_extensionsfrom the build directory alone โ so a package can truthfully say it includes extensions and then list none. The workflow reads the list out offiles.tar.gzand writes it, and verification fails the release if the declared list and the shipped archives disagree.
Verification is a script rather than a workflow step, so none of the above has to be taken on trust:
It makes every check listed above against any .ldmp on disk โ an already published one included โ printing what it found and exiting non-zero with the list of things it refuses. The release workflow calls the same script, so a release can be reproduced locally without pushing a tag. It compares the packaged OSGi bundles against bundles/osgi/modules and complains if that directory is absent; pass --staged-dir <dir> to point it elsewhere.
To capture a specific local state including your PostgreSQL data โ useful for handing an environment to a colleague โ package while the stack is running and skip the volume cleanup. That is deliberately not what ships in releases.
โป๏ธ Resetting a Local Bundle¶
The Liferay Workspace plugin has no bundle reset task โ clean removes only the build directory, and the clean* family it adds leaves bundles/ untouched. Two tasks fill that gap:
# Light: database, OSGi state and Tomcat work. Deployed artifacts and logs are kept.
./gradlew resetBundleState
# Full: the above, plus client extensions, OSGi modules, pending deployments and logs.
./gradlew resetBundleFull
resetBundleFull preserves the activation key (osgi/modules/*.xml); removing it would mean re-licensing the bundle.
Logs are cleared only by the full reset. A light reset is often run to reproduce a problem, and the logs are the record of what was just seen.
Both refuse to run while anything is listening on port 8080, because deleting bundles/data under a live Tomcat corrupts the database rather than resetting it. Stop Liferay first.
After a full reset, redeploy with blade gw deploy.
โ ๏ธ Known Issues & Troubleshooting¶
Node.js Versioning Constraint¶
Liferay's internal build process enforces Node.js 22.22.2 (pinned via nodeVersion in build.gradle). Attempts to override this with newer Node.js versions via Gradle properties conflict with Liferay's build requirements. The microservice itself, running in Docker, is immune to this and uses whatever modern Node.js version its own Dockerfile specifies.
401 Unauthorized Loop (Account Lockout)¶
Liferay DXP locks out user accounts after 5 failed login attempts. During automated E2E testing, early connection attempts before authentication modules are fully active can lock out the admin user. Fix: The E2E Test Orchestrator auto-recovers. To fix manually, run this SQL query on your database:
UPDATE user_ SET lockout = false, lockoutDate = null, failedLoginAttempts = 0 WHERE emailaddress = 'test@liferay.com';
Harmless OptimisticLockException Logs¶
During initial boot, you may see OptimisticLockException for UserImpl. This is caused by parallel connections competing to increment the failedLoginAttempts counter. This exception is entirely harmless and expected.
Last Reviewed: 2026-10-21