Running phoss AP - phax/phoss-ap GitHub Wiki

Running phoss AP

This page describes how to build and run the phoss AP application.

Prerequisites

  • 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)

Building from Source

git clone https://github.com/phax/phoss-ap.git
cd phoss-ap
mvn clean package

This 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 -DskipTests

Database Setup

phoss AP requires either a PostgreSQL or a MySQL database. The schema is created automatically via Flyway on first startup.

Option 1: Docker (recommended for development)

A Docker Compose file is included for quickly starting both PostgreSQL and MySQL instances:

docker compose -f unittest-db-docker-compose.yml up -d

This 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 down

Option 2: Existing PostgreSQL

Create 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=ap

Option 3: Existing MySQL

Create 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=peppol

Running the Application

Standalone JAR

The 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.jar

The application starts on port 8080 by default. The AS4 endpoint is available at http://localhost:8080/as4.

Custom Configuration

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]/project

Run with the Spring profile:

java -jar phoss-ap-webapp/target/phoss-ap-webapp-x.y.z.jar \
  --spring.profiles.active=private

2. 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.jar

3. 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.jar

JVM Tuning

For production deployments:

java -Xms512m -Xmx2g \
     -Djava.security.egd=file:/dev/urandom \
     -jar phoss-ap-webapp/target/phoss-ap-webapp-x.y.z.jar

Docker Deployment

Building the Docker Image

The 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 .

Running with Docker

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-ap

Docker Compose (full stack)

A 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 -d

Data Directory

Since 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.

Running as a non-root User

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-ap

The 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.


Extensions and Custom Drivers

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_PATH to 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.


Minimum Configuration for Production

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.


Health Check

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 1

Multi-Instance Deployment

phoss 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 to true on exactly one instance to submit Peppol reports. Set to false on all other instances.
  • startup.recovery.enabled: Should be true on all instances (default).

Reverse Proxy

When running behind a reverse proxy (nginx, Apache, etc.), configure:

server.forward-headers-strategy=native
phase4.endpoint.address=https://your-public-hostname/as4

Example 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;
    }
}

Troubleshooting

Database Connection Failures

If the AP fails to start with a database connection error:

  1. Verify the database (PostgreSQL or MySQL) is running and accessible on the configured host/port
  2. Check that the database, user, and schema exist
  3. If using Docker, ensure the database container is fully started before the AP container

AS4 Message Dumps

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.

Debug Logging

Enable SQL and transaction debug logging:

phossap.jdbc.debug.sql=true
phossap.jdbc.debug.transactions=true
phossap.jdbc.debug.connections=true
⚠️ **GitHub.com Fallback** ⚠️