Packaging and Deployment
Overview
- What you’ll learn:
- How Eclipse P2 provisioning works and how to create Feature projects and Update Site projects for distributing iDempiere plugins
- How to build plugins with Maven/Tycho, set up continuous integration with GitHub Actions, and automate testing and deployment
- How to manage plugin versioning, dependencies, Docker-based deployment, and release management for production environments
- Prerequisites: Lesson 15 — Building Your First Plugin, Lesson 33 — Testing and Debugging Plugins, basic familiarity with Maven and Git
- Estimated reading time: 24 minutes
Introduction
A plugin that exists only on a developer’s workstation provides no business value. To be useful, plugins must be packaged, versioned, tested automatically, and deployed reliably to production servers. iDempiere leverages Eclipse’s P2 provisioning system for plugin distribution — the same technology that manages plugins in the Eclipse IDE itself. Combined with Maven/Tycho for builds and CI/CD pipelines for automation, you can establish a professional software delivery process for your iDempiere customizations.
This lesson covers the complete journey from source code to production deployment: organizing plugins into features, building P2 update sites, automating builds with continuous integration, managing versions and dependencies, and deploying to production environments including Docker containers.
The P2 Repository Concept
Eclipse P2 (Provisioning Platform) is a framework for installing, updating, and managing Eclipse-based applications and their plugins. iDempiere is built on Eclipse Equinox (an OSGi implementation), which makes P2 its native plugin distribution mechanism.
How P2 Works
A P2 repository is a structured directory (local or hosted on a web server) containing:
- Plugin JARs: The compiled OSGi bundles containing your code.
- Feature JARs: Metadata that groups related plugins into installable units.
- content.xml / content.jar: An index of all installable units (plugins and features) in the repository, including their versions and dependencies.
- artifacts.xml / artifacts.jar: An index of the physical artifact files (JARs) and their download locations.
When you install a plugin from a P2 repository, the P2 engine resolves all dependencies, downloads the required artifacts, and configures the OSGi runtime to include the new bundles. This ensures that plugins are installed with all their prerequisites satisfied.
P2 vs. Manual JAR Deployment
While you can deploy plugins by manually copying JAR files to the plugins/ directory, P2 provides critical advantages:
- Dependency resolution: P2 ensures all required bundles are present before installation.
- Version management: P2 tracks installed versions and can upgrade, downgrade, or uninstall cleanly.
- Atomic updates: P2 either installs everything successfully or rolls back, preventing partial installations.
- Multiple repositories: You can configure multiple P2 repositories (iDempiere core + your custom plugins) and P2 resolves dependencies across all of them.
Feature Projects
A Feature project groups one or more plugins into a single installable unit. Think of it as a “product package” — users install the feature, and all included plugins are deployed together.
Creating a Feature Project
- In Eclipse, select File > New > Project > Plug-in Development > Feature Project.
- Name it following the convention:
com.example.feature. - On the “Referenced Plug-ins” page, select all plugins that belong to this feature.
The feature.xml File
The feature.xml file defines the feature’s metadata and contents:
<?xml version="1.0" encoding="UTF-8"?>
<feature
id="com.example.feature"
label="Example iDempiere Customization"
version="1.0.0.qualifier"
provider-name="Example Corp">
<description>
Custom business logic and reports for Example Corp's
iDempiere deployment.
</description>
<copyright>
Copyright (c) 2025 Example Corp. All rights reserved.
</copyright>
<license url="https://www.example.com/license">
[License text here]
</license>
<!-- Required iDempiere features (dependencies) -->
<requires>
<import feature="org.adempiere.server" version="11.0.0" match="greaterOrEqual"/>
<import feature="org.adempiere.ui.zk" version="11.0.0" match="greaterOrEqual"/>
</requires>
<!-- Plugins included in this feature -->
<plugin
id="com.example.model"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
<plugin
id="com.example.process"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
<plugin
id="com.example.reports"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
<plugin
id="com.example.rest"
download-size="0"
install-size="0"
version="0.0.0"
unpack="false"/>
</feature>
Feature Organization Strategies
For large customizations, consider splitting into multiple features:
- com.example.core.feature: Model classes, validators, and core business logic used by all other components.
- com.example.ui.feature: UI customizations, dashboard gadgets, and custom forms (depends on core).
- com.example.reports.feature: JasperReports and report processes (depends on core).
- com.example.rest.feature: Custom REST API endpoints (depends on core).
This separation allows deploying only the components needed for a specific environment (e.g., the REST feature might only be deployed on the API server, not on the user-facing server).
Update Site Project
An Update Site project builds the P2 repository from your features and plugins.
Creating an Update Site Project
- In Eclipse, select File > New > Project > Plug-in Development > Update Site Project.
- Name it:
com.example.p2. - Open the
site.xmlfile and add your features.
The site.xml File
<?xml version="1.0" encoding="UTF-8"?>
<site>
<feature url="features/com.example.feature_1.0.0.qualifier.jar"
id="com.example.feature"
version="1.0.0.qualifier">
<category name="example-customization"/>
</feature>
<category-def name="example-customization"
label="Example Corp Customization">
<description>Custom plugins for Example Corp deployment</description>
</category-def>
</site>
Building the Update Site Manually
In Eclipse, open the site.xml editor and click Build All. Eclipse compiles all plugins and features, and generates the P2 repository artifacts in the update site project directory. The result is a directory structure like:
com.example.p2/
features/
com.example.feature_1.0.0.202501150900.jar
plugins/
com.example.model_1.0.0.202501150900.jar
com.example.process_1.0.0.202501150900.jar
com.example.reports_1.0.0.202501150900.jar
com.example.rest_1.0.0.202501150900.jar
content.jar
artifacts.jar
Building with Maven/Tycho
Manual Eclipse builds are fine for development, but automated builds require Maven with the Tycho plugin. Tycho teaches Maven to understand Eclipse plugin projects, feature projects, and P2 repositories.
Parent POM
Create a parent POM at the root of your project repository:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.example</groupId>
<artifactId>com.example.parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>pom</packaging>
<properties>
<tycho.version>4.0.4</tycho.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<idempiere.target>https://download.idempiere.org/11/p2/</idempiere.target>
</properties>
<modules>
<module>com.example.model</module>
<module>com.example.process</module>
<module>com.example.reports</module>
<module>com.example.rest</module>
<module>com.example.model.test</module>
<module>com.example.feature</module>
<module>com.example.p2</module>
</modules>
<repositories>
<repository>
<id>idempiere</id>
<url>${idempiere.target}</url>
<layout>p2</layout>
</repository>
</repositories>
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-maven-plugin</artifactId>
<version>${tycho.version}</version>
<extensions>true</extensions>
</plugin>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>target-platform-configuration</artifactId>
<version>${tycho.version}</version>
<configuration>
<environments>
<environment>
<os>linux</os>
<ws>gtk</ws>
<arch>x86_64</arch>
</environment>
</environments>
</configuration>
</plugin>
</plugins>
</build>
</project>
Plugin POM
Each plugin project gets a minimal POM that inherits from the parent:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>com.example.parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.example.model</artifactId>
<packaging>eclipse-plugin</packaging>
</project>
Feature POM
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>com.example.parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.example.feature</artifactId>
<packaging>eclipse-feature</packaging>
</project>
Update Site POM
<project>
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>com.example</groupId>
<artifactId>com.example.parent</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<artifactId>com.example.p2</artifactId>
<packaging>eclipse-repository</packaging>
</project>
Building
Run the build from the root directory:
# Full build including tests
mvn clean verify
# Build without tests (faster)
mvn clean package -DskipTests
# Build and deploy to a local repository
mvn clean install
The P2 repository is generated in com.example.p2/target/repository/.
Continuous Integration Setup
Automating your build, test, and deployment pipeline ensures consistent quality and reduces manual errors.
GitHub Actions Workflow
Create a CI workflow at .github/workflows/build.yml:
name: Build and Test iDempiere Plugin
on:
push:
branches: [ main, develop ]
pull_request:
branches: [ main ]
jobs:
build:
runs-on: ubuntu-latest
services:
postgres:
image: postgres:15
env:
POSTGRES_USER: adempiere
POSTGRES_PASSWORD: adempiere
POSTGRES_DB: idempiere
ports:
- 5432:5432
options: >-
--health-cmd pg_isready
--health-interval 10s
--health-timeout 5s
--health-retries 5
steps:
- name: Checkout code
uses: actions/checkout@v4
- name: Set up JDK 17
uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Build with Maven
run: mvn clean verify -B
- name: Upload P2 Repository
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@v4
with:
name: p2-repository
path: com.example.p2/target/repository/
- name: Upload Test Results
if: always()
uses: actions/upload-artifact@v4
with:
name: test-results
path: '**/target/surefire-reports/*.xml'
Automated Testing in CI
Configure Tycho to run your JUnit tests during the build. For OSGi integration tests, use Tycho’s surefire plugin which launches a minimal OSGi runtime:
<!-- In the test plugin's pom.xml -->
<build>
<plugins>
<plugin>
<groupId>org.eclipse.tycho</groupId>
<artifactId>tycho-surefire-plugin</artifactId>
<version>${tycho.version}</version>
<configuration>
<testRuntime>p2Installed</testRuntime>
<argLine>-Xmx1g</argLine>
</configuration>
</plugin>
</plugins>
</build>
Versioning Strategy
Adopt semantic versioning for your plugins to communicate the nature of changes clearly:
Semantic Versioning (Major.Minor.Patch.Qualifier)
- Major (1.x.x): Breaking changes — removed or renamed APIs, incompatible database schema changes. Consumers of your plugin must update their code.
- Minor (x.1.x): New features — new endpoints, new model validators, additional fields. Backward-compatible with existing consumers.
- Patch (x.x.1): Bug fixes — corrected calculations, fixed null pointer exceptions, performance improvements. No API or behavior changes.
- Qualifier: Build metadata — typically a timestamp (e.g.,
1.2.3.202501150900) orSNAPSHOTfor development builds.
Version Management Across Projects
Keep versions synchronized across your plugin, feature, and update site. Use the Tycho versions plugin to update versions consistently:
# Update all project versions to 1.2.0-SNAPSHOT
mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=1.2.0-SNAPSHOT
# Before release, set the release version
mvn org.eclipse.tycho:tycho-versions-plugin:set-version -DnewVersion=1.2.0
Version Ranges in Dependencies
When your feature declares dependencies on iDempiere core, use appropriate version ranges:
<!-- In feature.xml -->
<requires>
<!-- Compatible with any 11.x release -->
<import feature="org.adempiere.server" version="11.0.0" match="compatible"/>
</requires>
<!-- In MANIFEST.MF -->
Require-Bundle: org.adempiere.base;bundle-version="[11.0.0,12.0.0)"
The range [11.0.0,12.0.0) means “any version from 11.0.0 (inclusive) up to but not including 12.0.0.” This allows your plugin to work with any patch or minor update of iDempiere 11 without requiring rebuilds.
Deploying via P2
Once you have a P2 repository, deploy plugins to iDempiere servers using the P2 director.
Using the P2 Director
The P2 director is a command-line application for installing features from P2 repositories:
# Install your feature from a local P2 repository
/opt/idempiere/idempiere -nosplash -application org.eclipse.equinox.p2.director \
-repository file:///path/to/p2-repository/ \
-installIU com.example.feature.feature.group \
-destination /opt/idempiere \
-profile DefaultProfile
# Install from a remote HTTP repository
/opt/idempiere/idempiere -nosplash -application org.eclipse.equinox.p2.director \
-repository https://plugins.example.com/p2/ \
-installIU com.example.feature.feature.group \
-destination /opt/idempiere \
-profile DefaultProfile
# Uninstall a feature
/opt/idempiere/idempiere -nosplash -application org.eclipse.equinox.p2.director \
-uninstallIU com.example.feature.feature.group \
-destination /opt/idempiere \
-profile DefaultProfile
Hosting a P2 Repository
Serve your P2 repository via any HTTP server:
# Simple approach: host via nginx
server {
listen 443 ssl;
server_name plugins.example.com;
location /p2/ {
alias /var/www/p2-repository/;
autoindex on;
}
}
Upload the contents of com.example.p2/target/repository/ to the server, and the P2 director can install from the URL.
Docker-Based Deployment
Docker is increasingly popular for iDempiere deployments. Integrate your plugins into a custom Docker image:
Dockerfile for iDempiere with Custom Plugins
FROM idempiereofficial/idempiere:11
# Copy P2 repository into a temporary directory
COPY p2-repository/ /tmp/p2-repo/
# Install plugins using P2 director
RUN /opt/idempiere/idempiere -nosplash \
-application org.eclipse.equinox.p2.director \
-repository file:///tmp/p2-repo/ \
-installIU com.example.feature.feature.group \
-destination /opt/idempiere \
-profile DefaultProfile \
&& rm -rf /tmp/p2-repo/
# Copy any additional configuration
COPY custom-config/ /opt/idempiere/custom-config/
Docker Compose for Development
version: '3.8'
services:
db:
image: postgres:15
environment:
POSTGRES_USER: adempiere
POSTGRES_PASSWORD: adempiere
POSTGRES_DB: idempiere
volumes:
- pgdata:/var/lib/postgresql/data
ports:
- "5432:5432"
idempiere:
build: .
depends_on:
- db
environment:
DB_HOST: db
DB_PORT: 5432
DB_NAME: idempiere
DB_USER: adempiere
DB_PASS: adempiere
ports:
- "8080:8080"
- "8443:8443"
- "4444:4444" # Debug port
volumes:
- idempiere-data:/opt/idempiere/data
volumes:
pgdata:
idempiere-data:
Release Management
A disciplined release process ensures reliable deployments.
Release Workflow
- Feature development: Work on feature branches, merged to
developvia pull requests with code review. - Release preparation: Create a release branch from
develop. Update version numbers (remove SNAPSHOT qualifier). Run full test suite. - Build release artifacts: Build the P2 repository from the release branch. Tag the commit with the version number.
- Deploy to staging: Install the release on a staging environment. Run integration tests and user acceptance testing.
- Deploy to production: Install the release on production using the P2 director. Verify functionality.
- Post-release: Merge the release branch to
mainand back todevelop. Bump version ondevelopto the next SNAPSHOT.
GitHub Actions Release Workflow
name: Release Plugin
on:
push:
tags:
- 'v*'
jobs:
release:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-java@v4
with:
java-version: '17'
distribution: 'temurin'
cache: 'maven'
- name: Build release
run: mvn clean verify -B
- name: Create GitHub Release
uses: softprops/action-gh-release@v1
with:
files: |
com.example.p2/target/com.example.p2-*.zip
generate_release_notes: true
- name: Deploy P2 repository
run: |
# Upload P2 repo to your hosting server
rsync -avz com.example.p2/target/repository/ \
deploy@plugins.example.com:/var/www/p2-repository/
Dependency Management Between Plugins
When multiple teams or projects develop iDempiere plugins, managing inter-plugin dependencies becomes important.
Declaring Dependencies
Use Require-Bundle for compile-time dependencies on specific bundles, and Import-Package for loose coupling:
# Tight coupling: depends on a specific bundle
Require-Bundle: com.example.core;bundle-version="[1.0.0,2.0.0)"
# Loose coupling: depends on the package, not the bundle
Import-Package: com.example.core.api;version="[1.0.0,2.0.0)"
Import-Package is generally preferred because it allows the implementation bundle to change without affecting consumers. Use Require-Bundle only when you need access to multiple packages from a specific bundle or when using the bundle’s extension points.
API Stability Guidelines
- Export only API packages: In your
MANIFEST.MF, only export packages that form your public API. Keep implementation classes in non-exported packages. - Use interfaces: Define service interfaces in API packages and implementations in internal packages. Consumers depend on the interfaces, not the implementations.
- Document breaking changes: When you must make a breaking change, increment the major version and document migration steps in your changelog.
Practical Example: CI/CD Pipeline for an iDempiere Plugin
Here is a complete example tying everything together for a real-world workflow:
Repository Structure
my-idempiere-plugins/
com.example.model/ # Core model classes and validators
src/
META-INF/MANIFEST.MF
pom.xml
com.example.process/ # Custom processes
src/
META-INF/MANIFEST.MF
pom.xml
com.example.rest/ # REST API endpoints
src/
META-INF/MANIFEST.MF
pom.xml
com.example.model.test/ # Test bundle
src/
META-INF/MANIFEST.MF
pom.xml
com.example.feature/ # Feature project
feature.xml
pom.xml
com.example.p2/ # Update site
category.xml
pom.xml
.github/
workflows/
build.yml # CI workflow
release.yml # Release workflow
Dockerfile # Docker build
docker-compose.yml # Local dev environment
pom.xml # Parent POM
Developer Workflow
- Developer creates a feature branch:
git checkout -b feature/new-validation - Developer writes code and tests locally, running
mvn clean verify. - Developer pushes the branch and creates a pull request.
- GitHub Actions automatically builds and runs tests on the PR.
- Code is reviewed and merged to
develop. - When ready for release, a maintainer tags the commit:
git tag v1.2.0. - The release workflow builds the P2 repository and creates a GitHub release.
- The operations team deploys to staging using the P2 director, then promotes to production.
Summary
You have now learned the complete lifecycle of iDempiere plugin distribution — from organizing plugins into features and building P2 repositories with Maven/Tycho, through automated CI/CD pipelines with GitHub Actions, to production deployment via P2 director and Docker. Combined with semantic versioning, dependency management, and disciplined release processes, these practices enable you to deliver iDempiere customizations with the same rigor as any professional software product. This concludes the Advanced module of the iDempiere learning curriculum. You now have the knowledge to architect, develop, test, and deploy production-grade iDempiere plugins and integrations.