Running phoss AP - phax/phoss-ap GitHub Wiki
This page describes how to build and run the phoss AP application.
- Java 21 or later
- Apache Maven 3.9+
- PostgreSQL 14+ or MySQL 8.0+ (as the database backend)
- Docker (optional, for database or containerized deployment)
git clone https://github.com/phax/phoss-ap.git
cd phoss-ap
mvn clean packageThis produces an executable Spring Boot JAR at phoss-ap-webapp/target/phoss-ap-webapp-<version>.jar.
To skip tests during the build:
mvn clean package -DskipTestsphoss AP requires either a PostgreSQL or a MySQL database. The schema is created automatically via Flyway on first startup.
A Docker Compose file is included for quickly starting both PostgreSQL and MySQL instances:
docker compose -f unittest-db-docker-compose.yml up -dThis starts:
-
PostgreSQL on port 5432 — Database:
phoss-ap, User:peppol, Password:peppol -
MySQL on port 3306 — Database:
phoss-ap, User:peppol, Password:peppol
To stop and remove the databases:
docker compose -f unittest-db-docker-compose.yml downCreate a database and user manually:
CREATE DATABASE "phoss-ap";
CREATE USER peppol WITH PASSWORD 'peppol';
GRANT ALL PRIVILEGES ON DATABASE "phoss-ap" TO peppol;Then configure the connection in application.properties:
phossap.jdbc.database-type=postgresql
phossap.jdbc.driver=org.postgresql.Driver
phossap.jdbc.url=jdbc:postgresql://your-host:5432/phoss-ap
phossap.jdbc.user=peppol
phossap.jdbc.password=peppol
phossap.jdbc.schema=apCreate a database and user manually:
CREATE DATABASE `phoss-ap`;
CREATE USER 'peppol'@'%' IDENTIFIED BY 'peppol';
GRANT ALL PRIVILEGES ON `phoss-ap`.* TO 'peppol'@'%';Then configure the connection in application.properties:
phossap.jdbc.database-type=mysql
phossap.jdbc.driver=com.mysql.cj.jdbc.Driver
phossap.jdbc.url=jdbc:mysql://your-host:3306/phoss-ap?useUnicode=true&useJDBCCompliantTimezoneShift=true&useLegacyDatetimeCode=false
phossap.jdbc.user=peppol
phossap.jdbc.password=peppolThe simplest way to run phoss AP (replace x.y.z with the actual version number, e.g. 0.2.0):
java -jar phoss-ap-webapp/target/phoss-ap-webapp-x.y.z.jarThe application starts on port 8080 by default. The AS4 endpoint is available at http://localhost:8080/as4.
phoss AP uses the ph-config configuration resolution. Properties can be provided in several ways:
1. Private properties file (recommended for secrets):
Create application-private.properties in the classpath or working directory:
peppol.owner.seatid=POP000306
org.apache.wss4j.crypto.merlin.keystore.file=/path/to/your-keystore.p12
org.apache.wss4j.crypto.merlin.keystore.password=your-password
phase4.api.requiredtoken=your-secret-token
sentry.dsn=https://[email protected]/projectRun with the Spring profile:
java -jar phoss-ap-webapp/target/phoss-ap-webapp-x.y.z.jar \
--spring.profiles.active=private2. System properties (command line):
java -Dpeppol.owner.seatid=POP000306 \
-Dphossap.jdbc.url=jdbc:postgresql://db-host:5432/phoss-ap \
-jar phoss-ap-webapp/target/phoss-ap-webapp-x.y.z.jar3. Environment variables:
Properties can also be provided as environment variables by replacing dots with underscores and uppercasing (Spring Boot relaxed binding):
export PHOSSAP_JDBC_URL=jdbc:postgresql://db-host:5432/phoss-ap
export PEPPOL_OWNER_SEATID=POP000306
java -jar phoss-ap-webapp/target/phoss-ap-webapp-x.y.z.jarFor production deployments:
java -Xms512m -Xmx2g \
-Djava.security.egd=file:/dev/urandom \
-jar phoss-ap-webapp/target/phoss-ap-webapp-x.y.z.jarThe Dockerfile uses a multi-stage build that compiles the application from source inside Docker — no local build tools are required:
docker build -t phelger/phoss-ap .docker run -d \
--name phoss-ap \
-p 8080:8080 \
-e PHOSSAP_JDBC_URL=jdbc:postgresql://host.docker.internal:5432/phoss-ap \
-e PHOSSAP_JDBC_USER=peppol \
-e PHOSSAP_JDBC_PASSWORD=peppol \
-e PEPPOL_OWNER_SEATID=POP000306 \
-v /path/to/keystore:/config/keystore.p12:ro \
-v /path/to/data:/var/phoss-ap/data \
phelger/phoss-apA docker-compose.yml for running phoss AP with PostgreSQL:
services:
postgres:
image: postgres:latest
restart: always
ports:
- "5432:5432"
environment:
POSTGRES_DB: phoss-ap
POSTGRES_USER: peppol
POSTGRES_PASSWORD: peppol
volumes:
- pg18data:/var/lib/postgresql
phoss-ap:
image: phelger/phoss-ap
restart: always
ports:
- "8080:8080"
environment:
PHOSSAP_JDBC_URL: jdbc:postgresql://postgres:5432/phoss-ap
PHOSSAP_JDBC_USER: peppol
PHOSSAP_JDBC_PASSWORD: peppol
PEPPOL_STAGE: test
PEPPOL_OWNER_SEATID: POP000306
PHASE4_ENDPOINT_ADDRESS: https://your-ap.example.com/as4
FORWARDING_MODE: http_post_async
FORWARDING_HTTP_ENDPOINT: http://your-backend:8888/forwarding
volumes:
- ap-data:/var/phoss-ap/data
- ./keystore.p12:/config/keystore.p12:ro
healthcheck:
test: ["CMD-SHELL", "wget -qO- http://localhost:8080/actuator/health || exit 1"]
interval: 30s
timeout: 5s
retries: 3
depends_on:
- postgres
volumes:
pg18data:
ap-data:Start:
docker compose up -dSince 0.12.0. The image sets GLOBAL_DATAPATH=/var/phoss-ap/data/, so all runtime data — stored
documents and AS4 dumps — is written below the /var/phoss-ap/data volume that the image declares.
Mount a named volume or a host directory there to keep the data across container recreations.
Before 0.12.0 the image had no such default, and the global.datapath=generated/ value of the
bundled application.properties was resolved relative to the working directory /. Data therefore
ended up in /generated inside the container — outside of every volume, and lost when the container
was removed. If you are upgrading from an older image and mounted /var/phoss-ap/data without also
setting GLOBAL_DATAPATH, check the old location for data first.
Since 0.12.0. The image runs as root by default, but can be run as an arbitrary non-root UID, as
required by Kubernetes namespaces enforcing the restricted PodSecurity profile and by OpenShift:
docker run -d --name phoss-ap -p 8080:8080 \
--user 10001:0 \
-e PHOSSAP_JDBC_URL=jdbc:postgresql://host.docker.internal:5432/phoss-ap \
-e PHOSSAP_JDBC_USER=peppol \
-e PHOSSAP_JDBC_PASSWORD=peppol \
phelger/phoss-apThe Kubernetes equivalent:
securityContext:
runAsNonRoot: true
runAsUser: 10001
allowPrivilegeEscalation: false
capabilities:
drop:
- ALL/app.jar is world readable, and /ext and /var/phoss-ap/data are group owned by GID 0 with the
group permissions mirroring the owner permissions — GID 0 is the group Kubernetes uses when
runAsGroup is not set. Run with that group (--user <uid>:0 in Docker) or make the mounted data
volume writable for the pod in another way, e.g. via fsGroup.
A bind-mounted host directory keeps its host ownership, so it must be writable for the UID the container runs as. This does not apply to named volumes, which inherit the ownership from the image.
Since 0.10.4. Custom SPI implementations (notification handlers, verifiers, document forwarders, …) and additional JDBC drivers can be added without rebuilding the application jar. Drop a thin jar into the extension directory that the AP puts on its classpath at startup:
-
Standalone JAR — set
LOADER_PATHto a directory of extra jars:LOADER_PATH=./ext java -jar phoss-ap-webapp-x.y.z.jar
-
Docker — the image already sets
LOADER_PATH=/ext, so bind-mount a directory of jars:docker run -d --name phoss-ap -p 8080:8080 \ -v /path/to/ext:/ext \ ... \ phelger/phoss-ap
See Runtime Extensions for the full mechanism, the list of available SPIs, packaging rules, and a Docker "bake into a derived image" recipe.
The following properties must be configured before running in production:
| Property | Description |
|---|---|
peppol.stage |
Set to prod for the production Peppol network |
peppol.owner.seatid |
Your assigned Peppol Seat ID (e.g. POP000306) |
peppol.owner.countrycode |
2-letter country code of the AP operator |
phase4.endpoint.address |
The public AS4 endpoint URL (e.g. https://ap.example.com/as4) |
org.apache.wss4j.crypto.merlin.keystore.file |
Path to your Peppol AP certificate keystore |
org.apache.wss4j.crypto.merlin.keystore.password |
Keystore password |
org.apache.wss4j.crypto.merlin.keystore.alias |
Key alias within the keystore |
org.apache.wss4j.crypto.merlin.keystore.private.password |
Private key password |
org.apache.wss4j.crypto.merlin.truststore.file |
Set to truststore/2025/ap-prod-truststore.p12 for production |
smpclient.truststore.path |
Set to truststore/2025/smp-prod-truststore.p12 for production |
phossap.jdbc.database-type |
PostgreSQL or MySQL
|
phossap.jdbc.url |
Database JDBC URL |
phossap.jdbc.user |
Database user |
phossap.jdbc.password |
Database password |
phase4.api.requiredtoken |
A strong, random API token |
forwarding.mode |
One of: http_post_sync, http_post_async, s3_link, sftp, filesystem
|
forwarding.http.endpoint |
(if HTTP mode) URL of the Receiver Backend |
See Configuration Properties for the full reference.
The Spring Boot Actuator health endpoint is available at:
GET http://localhost:8080/actuator/health
This can be used for Docker health checks, load balancer probes, and monitoring.
The official Docker image already defines a HEALTHCHECK on this endpoint:
HEALTHCHECK --interval=30s --timeout=5s --retries=3 \
CMD wget -qO- http://localhost:8080/actuator/health || exit 1phoss AP supports running multiple instances against the same database (PostgreSQL or MySQL). All schedulers (retry, archival) use SELECT ... FOR UPDATE SKIP LOCKED for safe concurrent work distribution. See Architecture Overview for details.
Key settings for multi-instance:
-
peppol.reporting.schedule.enabled: Set totrueon exactly one instance to submit Peppol reports. Set tofalseon all other instances. -
startup.recovery.enabled: Should betrueon all instances (default).
When running behind a reverse proxy (nginx, Apache, etc.), configure:
server.forward-headers-strategy=native
phase4.endpoint.address=https://your-public-hostname/as4Example nginx configuration:
server {
listen 443 ssl;
server_name ap.example.com;
location / {
proxy_pass http://localhost:8080;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
client_max_body_size 100M;
}
}If the AP fails to start with a database connection error:
- Verify the database (PostgreSQL or MySQL) is running and accessible on the configured host/port
- Check that the database, user, and schema exist
- If using Docker, ensure the database container is fully started before the AP container
Enable AS4 message dumps for debugging:
phase4.dump.path=/var/phoss-ap/data/phase4-dumps/This writes all incoming and outgoing AS4 messages to disk for inspection.
Enable SQL and transaction debug logging:
phossap.jdbc.debug.sql=true
phossap.jdbc.debug.transactions=true
phossap.jdbc.debug.connections=true