Table of Contents

Running as a Container

PAS is released both as direct installers and as a Docker image. To run the image as a container, you will need to provide license files via a Docker volume.

This can either be done by mounting a volume directly, or by mounting a container overlay. Both approaches have different merits, as will be explained below.

Mounting a volume directly

The advantage of mounting a volume directly is that you may mount a configuration file where you can make changes in runtime and have them persisted, just like if you installed via an installer.

This can however also be a disadvantage when you want to re-use a base configuration across many containers, perhaps with the help of environment variables, without wanting your source config file to be changed. To avoid that, use the container overlay.

Here is an example docker-compose file that will deploy the PAS container. It mounts the directory /my/license/dir to the PAS license directory. For this deployment to work, a valid license will need to be placed in /my/license/dir on the host machine. Here, a configuration is also mounted in a similar fashion.

services:
    pas:
        container_name: pas
        image: phenixid/pas:5.1.1
        ports:
            - "443:8443"
            - "80:8080"
        environment:
            PAS_LOG_LEVELS: com.phenixidentity=INFO;org.opensaml.xml=OFF;
        volumes:
            - "/my/license/dir:/opt/phenixid/license"
            - "/my/config/dir:/opt/phenixid/config"

Mounting a container overlay

The container overlay is mounted as a volume towards the directory /container_overlay inside the PAS directory. Before starting, all files from inside container_overlay are copied into their respective directory inside the PAS directory.

So if you have your license file at /container_overlay/license/license.p12, it will be copied over into /license/license.p12. The files are not only copied over, but they will replace a certain syntax with corresponding environment variables. Files can also be parsed into base64 to allow for easy mounting of keystores and metadata.

The syntax for the environment variable replacement {{ PAS_MYVAR }} (where PAS_MYVAR is an environment variable included in the container), and you may place that in any file in the container overlay. The environment variable must start with PAS_ for the replacement to be done.

The syntax for the file base64 replacement is {{ RESOURCE_FILE:myfile.p12 }}. The file is expected to be located at /container_overlay, but can be located in a subdirectory, as long as that is included in the placeholder. For example: {{ RESOURCE_FILE:keystores/myfile.p12 }} means that myfile.p12 should be located at container_overlay/keystores/myfile.p12. On container start, the file will be read and the placeholder will be replaced with the base64 content of the file. Make sure that if you are using this to mount a keystore, you also set environment variable placeholders for private key password and other things that will be needed to use the keystore.

As an example, this is what it could look like to mount a keystore via the container overlay in phenix-store.json:

For the keystore:

{
        "id" : "d46188f9-d3ae-4837-85a2-047d51a5b178",
        "type" : "pkcs12",
        "password" : "{{ PAS_MYKEYSTORE_PASSWORD }}",
        "certificateAlias" : "1",
        "privateKeyPassword" : "{{ PAS_MYKEYSTORE_PRIVKEYPASSWORD }}",
        "resource" : "d8d27fdd-46c9-4642-b1a0-1a7a3381146e",
        "name" : "MyKeystore",
        "guide_ref" : "3540e993-d8bd-4ac2-ac1b-98da34576dab",
        "modified" : "2024-06-11T12:36:01.910Z"
    }

For the resource:

{
    "id" : "d8d27fdd-46c9-4642-b1a0-1a7a3381146e",
    "content_type" : "application/x-pkcs12",
    "content_encoding" : "base64",
    "content": "{{ RESOURCE_FILE:mykeystore.p12 }}",
    "guide_ref" : "5ce04202-0100-426b-8ce1-95e8bbdae3ff",
    "modified" : "2024-05-29T11:44:53.288Z"
}

An example docker-compose.yml file that utilizes container overlay is seen below. The environment variable PAS_MY_KEYSTORE is used and will replace the content in the example above. Its value here is taken from the host machine's environment.

services:
    pas:
        container_name: pas
        image: phenixid/pas:5.1.1
        ports:
            - "443:8443"
            - "80:8080"
        environment:
            PAS_MYKEYSTORE_PASSWORD: "something secret, usually mounted via local environment variables like the line below"
            PAS_MYKEYSTORE_PRIVKEYPASSWORD: $MY_KEYSTORE_PRIVKEYPASSWORD
            PAS_LOG_LEVELS: com.phenixidentity=INFO;org.opensaml.xml=OFF;
        volumes:
            - "/my/path/to/container_overlay:/opt/phenixid/container_overlay"

So if you mount a container overlay like this, your original configuration files will not be modified and can be reused freely. A drawback to this is that any changes you make (e.g. to the configuration) during runtime will be lost upon restart of the container. But the point of this approach is to not make any runtime changes to the configuration, the base configuration file should contain everything you want to run.

Note that you can still use the Store API and orchestration tools to alter the configuration in combination with this, but all runtime changes will be lost upon restart, meaning runtime changes should be reflected in the base config as well.

To create this base configuration you may want to first run the directly mounted volume in a test environment, create your configuration with the help of the GUI, and then use that configuration in your container overlay once you are finished.

Run as non-root

You can change the username, UID and GID by building your own image based on the PAS image.

FROM phenixid/pas:5.1.1

ARG USER=phenixid
ARG UID=1000
ARG GID=1000

# Create user and change ownership of files and folders inside the image
RUN /opt/phenixid/bin/change-container-user.sh --user "$USER" --uid "$UID" --gid "$GID"

USER $UID
docker build . -t myrepo/pas:5.1.1-non-root

Differences between standalone and container

There are a few differences in behaviour between then running as a standalone application (installed using the installer) and when running as a container:

  • The standalone version will by default configure the JVM to have a heap size ("memory usage") of 4GB, which can be overridden by the environment variable JAVA_HEAP_SIZE (when running on Linux) - this does not apply to containers, containers do not honour this environment variable

Custom VM options

When running as a container, you might want to set custom VM options in order to customize the JVM, for example:

  • Setting memory limits for PAS, ex:
    • Relative to container memory limits: -XX:MaxRAMPercentage
    • As fixed min- and max memory limits: -Xms and -Xmx
    • Modifying GC behavior for performance tuning
  • Setting other custom VM options, ex. custom timeouts mentioned in other parts of the documentation (Extra VM options)

This can be achieved by setting the environment variable PAS_EXTRA_VM_OPTIONS for the container. The VM options are passed to the JVM in the following order:

  • PAS system defaults (for the exact content, see the startup script /opt/phenixid/bin/start-PhenixID.sh)
  • The content of the file config/extraoptions.vmoptions (if it exists)
  • The content of the environment variable PAS_EXTRA_VM_OPTIONS

In general, the JVM allows a previously set value to be overwritten by a newer value being set.

Example, let PAS use 70% of the container's memory restrictions for heap:

PAS_EXTRA_VM_OPTIONS="-XX:MaxRAMPercentage=70 -XX:InitialRAMPercentage=70 -XX:MinRAMPercentage=50"

Logging

When running as a container, a different log appender is used to allow for easier management of docker logs. You may also use environment variables to control log levels as in the docker-compose.yml examples above. Read the specifics regarding logging when running as a container here.

The logs are written to standard output instead of logging to a file, and it's written as JSON. Each log entry is a single line of text.

Event logs

Event logs are written using the logger_name named EVENT. In the example below, both legacy events and new events are shown (legacy events are not enabled by default):

  • The legacy events are CEF-formatted inside the message.
    • The mdc is empty
    • The timestamp in the CEF-message is when the event was issued
    • The timestamp in JSON is when the event was written to standard output - might be a delay on a busy system
  • New events use the human friendly event name as the message
    • The event_id is part of the mdc - this is the machine-readable event id
    • Additional event parameters (specific for each event, see Audit Logs: Overview for details) are also placed inside the mdc
    • The timestamp is when the log was issued, not when it was written
    • level is the log level for the event, see Audit logs / Event logs in PAS 6.0 and above for details
{"mdc":{},"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"2025-04-08T15:18:03+02:00 DESKTOP-J6HPGV6 CEF:0|PhenixID|PAS|6.0.0-SNAPSHOT|EVT_000002|Server starting|2|","thread_name":"vert.x-eventloop-thread-17","@timestamp":"2025-04-08T15:18:03.729+0200","logger_name":"EVENT"}
{"mdc":{"event_id":"core_server_run_starting"},"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"Server starting","thread_name":"vert.x-eventloop-thread-17","@timestamp":"2025-04-08T15:18:03.724+0200","level":"NOTICE","logger_name":"EVENT"}
{"mdc":{},"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"2025-04-08T15:18:18+02:00 DESKTOP-J6HPGV6 CEF:0|PhenixID|PAS|6.0.0-SNAPSHOT|EVT_000003|Server started|2|msg=Some operations may still be pending","thread_name":"vert.x-eventloop-thread-17","@timestamp":"2025-04-08T15:18:18.771+0200","logger_name":"EVENT"}
{"mdc":{"event_id":"core_server_run_started"},"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"Server started","thread_name":"vert.x-eventloop-thread-17","@timestamp":"2025-04-08T15:18:18.772+0200","level":"NOTICE","logger_name":"EVENT"}
{"mdc":{},"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"2025-04-08T15:19:07+02:00 DESKTOP-J6HPGV6 CEF:0|PhenixID|PAS|6.0.0-SNAPSHOT|EVT_001006|User authentication success with username & password|2|destinationServiceName=config duser=phenixid phenixIDTraceId=#HQ5Sy2Szcp8Lg77U src=127.0.0.1","thread_name":"vert.x-eventloop-thread-17","@timestamp":"2025-04-08T15:19:07.267+0200","logger_name":"EVENT"}
{"mdc":{"duser":"phenixid","event_id":"shared_legacy_userauthentication_success","phxHttpServerId":"2c62b02e-97cc-4060-8242-fd5b2b823929","phxLegacyAuthenticatorIdOrAlias":"config","phxLegacyAuthenticatorType":"com.phenixidentity.authentication.handler.internal.DefaultInternalAuthenticator","phxTraceId":"#HQ5Sy2Szcp8Lg77U","requestClientApplication":"Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/135.0.0.0 Safari/537.36","sourceServiceName":"config","src":"127.0.0.1"},"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"Legacy user authentication success","thread_name":"vert.x-eventloop-thread-17","@timestamp":"2025-04-08T15:19:07.267+0200","level":"NOTICE","logger_name":"EVENT"}

Server logs

  • timestamp is when the log entry was written, not when it was issued - there might be a delay on a busy system
  • level is the "log4j-log level"
  • mdc might contain a trace_id, if applicable
{"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"Initialization completed in 2328 ms (async operations may still be pending)","thread_name":"isolated-worker-pool-com.phenixidentity~phenix-pipes-6-1","@timestamp":"2025-04-08T15:18:03.674+0200","level":"INFO","logger_name":"com.phenixidentity.pipes.PipesVerticle"}
{"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"DESKTOP-J6HPGV6.root: module 'com.phenixidentity~phenix-pipes' successfully deployed with id: 8f3ba70f-6ca3-4f23-835f-8f181d894ae6","thread_name":"isolated-worker-pool-com.phenixidentity~phenix-node-1-1","@timestamp":"2025-04-08T15:18:03.692+0200","level":"INFO","logger_name":"com.phenixidentity.core.modules.internal.ModuleDeployer"}
{"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"Configuring connections...","thread_name":"isolated-worker-pool-com.phenixidentity~phenix-pipes-6-1","@timestamp":"2025-04-08T15:18:03.692+0200","level":"INFO","logger_name":"com.phenixidentity.pipes.internal.ConnectionsManager"}
{"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"DESKTOP-J6HPGV6.root: Deployed 4 modules","thread_name":"isolated-worker-pool-com.phenixidentity~phenix-node-1-1","@timestamp":"2025-04-08T15:18:03.694+0200","level":"INFO","logger_name":"com.phenixidentity.core.modules.ModulesManager"}
{"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"All modules deployed: \r\n  system: null: root.root\r\n    DESKTOP-J6HPGV6.root: com.phenixidentity~phenix-httpclient-mod: 5bfa9444-d628-4b61-9d0f-f56d72d6f36a.0\r\n    DESKTOP-J6HPGV6.root: com.phenixidentity~phenix-crypto: 9c349dea-6bce-4ba6-835c-1a5d521e82ec.0\r\netc. etc.","thread_name":"isolated-worker-pool-com.phenixidentity~phenix-node-1-1","@timestamp":"2025-04-08T15:18:03.695+0200","level":"INFO","logger_name":"com.phenixidentity.node.internal.NodeManager"}
{"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"11 module(s) successfully deployed on node 'DESKTOP-J6HPGV6'","thread_name":"isolated-worker-pool-com.phenixidentity~phenix-node-1-1","@timestamp":"2025-04-08T15:18:03.696+0200","level":"INFO","logger_name":"com.phenixidentity.node.internal.NodeManager"}
{"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"Node 'DESKTOP-J6HPGV6' started successfully","thread_name":"isolated-worker-pool-com.phenixidentity~phenix-node-1-1","@timestamp":"2025-04-08T15:18:03.765+0200","level":"INFO","logger_name":"com.phenixidentity.node.NodeVerticle"}
{"mdc":{"trace_id":"#HQ5Sy2Szcp8Lg77U"},"@version":1,"source_host":"DESKTOP-J6HPGV6","message":"[mods\\com.phenixidentity~phenix-prism-guides\\js\\backend.js] Cannot resolve crypto defaulting to native","thread_name":"ScripHandler: /config/scenarios/api//backend.js_b85fee63-dc63-4bcf-bb7c-1c1a60f984e4-0","@timestamp":"2025-04-08T15:19:08.326+0200","level":"INFO","logger_name":"com.phenixidentity.prism.api.internal.handlers.ScriptHandler"}