Configuring Master

This page shows how to run Axelix Master in your own environment. Pick a shape that matches how you ship the rest of your services: a standalone JAR, a Docker container, a Docker Compose stack, or a Kubernetes deployment.

Before you start

A few facts to fix in your head before you copy any of the snippets below — they save debugging time later.

  • HTTP port: Master listens on 8080 by default (server.port).
  • Database: Master supports three engines, picked automatically from the JDBC URL prefix — SQLite (jdbc:sqlite:), PostgreSQL (jdbc:postgresql:), and MySQL (jdbc:mysql:). The default is SQLite, with a file named axelix.db next to the working directory. Liquibase runs schema migrations on every startup.
  • Front-end assets: the axelix-1.0.0.jar and the published Docker image both bundle the UI — there is nothing extra to wire up for the web interface.
  • Authentication: Master ships with a built-in super-admin account admin / admin and an unset JWT signing key. In any non-throwaway environment you must change both — see Configuration reference.

Configuration reference

Every Axelix Master property lives under the axelix.master.* namespace. The tables below list each one with its default value where one is set; entries marked (unset) must be supplied yourself before the corresponding feature can start.

The default super-admin credentials (admin / admin) grant full control over every service Master has discovered, and the JWT signing key has no default at all. Override both — together with axelix.master.auth.cookie.secure when serving over HTTPS — before exposing Master to anyone other than yourself.

Supplying properties

Master is a Spring Boot application, so every property on this page can be supplied through any source Spring Boot reads — not only a config file:

  • a YAML or properties config file (application.yaml / application.properties);
  • a JVM system property passed with -D on the java command line;
  • an environment variable.

Master never restricts a property to a single source. Wherever this page documents a property such as axelix.master.auth.jwt.signing-key, you may set it in whichever of the three fits your deployment — the same name reaches Master identically through all of them.

This works because of Spring Boot's relaxed binding: one logical property has several equivalent spellings, and Master reads them all as the same value. The canonical form used throughout this page is lowercase, dot-separated, kebab-case (axelix.master.auth.jwt.signing-key). The table below shows how that one property is written for each source:

SourceHow you write axelix.master.auth.jwt.signing-key
application.yamlnested keys axelix: → master: → auth: → jwt: → signing-key:
application.propertiesaxelix.master.auth.jwt.signing-key=...
JVM system property (-D)-Daxelix.master.auth.jwt.signing-key=...
Environment variableAXELIX_MASTER_AUTH_JWT_SIGNINGKEY=...

The environment-variable form follows a strict rule: upper-case the name, replace every dot with an underscore, and drop any dashes. That is why signing-key becomes SIGNINGKEY — the dash is removed, not turned into an underscore. A property with no dashes maps more directly: axelix.master.metrics.prometheus.enabled becomes AXELIX_MASTER_METRICS_PROMETHEUS_ENABLED.

A -Daxelix.master.auth.jwt.signing-key=... flag and an AXELIX_MASTER_AUTH_JWT_SIGNINGKEY=... environment variable are interchangeable — the same value reaches Master either way, so use whichever your platform makes easier. For secrets, prefer environment variables: the Docker and Docker Compose examples below use them because the JVM echoes the full JAVA_TOOL_OPTIONS value (the usual carrier for -D flags in a container) to stderr on startup, which would leak anything sensitive into the logs.

Pointing Master at a config file

ReleasedAvailable since release: 1.1.0

The three sources above cover most setups, but it is often the case that you would want to supply a configuration to the Axelix Master from the external YAML pr properties file, for example from a file mounted into a container at a custom path.

It can be achieved by providing the property named axelix.master.config.location to Axelix Master configuration.

Since Axelix Master is a Spring Boot app, it carries the full set of location shapes Spring Boot understands: a single file, a directory, or a comma-separated list of either, each optionally prefixed with file: or optional: (the latter tolerates a missing file instead of failing startup). A file named here adds to Master's bundled defaults rather than replacing them — the values it defines win, and everything it leaves out keeps its default.

PropertyDefaultDescription
axelix.master.config.locationemptyLocation of an external configuration file (or directory) to load on top of Master's defaults. Accepts a single path, a directory, or a comma-separated list, each with an optional file: / optional: prefix.

Because this property decides where configuration is read from, it has to be known before that reading happens. Supply it via a JVM system property or an environment variable — not from inside the very file it points to:

java -Daxelix.master.config.location=file:/etc/axelix/master.yaml -jar master.jar

or, for a container, pass it as an environment variable and mount the file in:

docker run --rm -p 8080:8080 \
  -e AXELIX_MASTER_CONFIG_LOCATION=file:/etc/axelix/master.yaml \
  -v /host/config/master.yaml:/etc/axelix/master.yaml:ro \
  ghcr.io/axelixlabs/axelix:1.0.0

Loading configuration from a Spring Cloud Config Server

ReleasedAvailable since release: 1.1.0

If you already run a Spring Cloud Config Server to centralise configuration across your fleet, Master can pull its own settings from it too. Master acts as a Spring Cloud Config client: on startup it fetches configuration for its application name and merges it in. Any axelix.master.* property on this page can therefore live in the Config Server's backing store instead of a local file.

The feature is off by default. Turn it on with axelix.master.external-config.spring-cloud-config.enabled=true and point Master at the server with the uri property.

PropertyDefaultDescription
axelix.master.external-config.spring-cloud-config.enabledfalseLoad configuration from a Spring Cloud Config Server on startup.
axelix.master.external-config.spring-cloud-config.uriemptyBase URL of the Config Server, for example http://config-server:8888. Required when enabled=true.
axelix.master.external-config.spring-cloud-config.nameemptyApplication name the server resolves configuration for — the {application} segment of its request path. When left empty, Master uses its own name, axelix.
axelix.master.external-config.spring-cloud-config.labelmasterThe {label} requested from the server, typically a Git branch or tag in the backing repository.
axelix.master.external-config.spring-cloud-config.usernameemptyBasic-auth username, when the Config Server requires HTTP Basic authentication.
axelix.master.external-config.spring-cloud-config.passwordemptyBasic-auth password, paired with username.

Like the config-file location above, these properties decide where configuration is read from, so they must be known before that reading starts. Supply them as JVM system properties or environment variables — not from a file the Config Server would itself serve. A minimal launch looks like this:

java \
  -Daxelix.master.external-config.spring-cloud-config.enabled=true \
  -Daxelix.master.external-config.spring-cloud-config.uri=http://config-server:8888 \
  -jar master.jar

When the feature is enabled, Master starts in fail-fast mode. If the Config Server cannot be reached at startup, Master refuses to boot rather than starting with incomplete configuration. While the feature stays off (the default), Master never contacts a server and boots normally.

Loading configuration from HashiCorp Vault

ReleasedAvailable since release: 1.1.0

If your organisation keeps secrets in HashiCorp Vault, Master can read its own configuration from there too. On startup Master fetches a secret from Vault's KV secrets engine and merges every key of that secret into its configuration, exactly as if those keys were written in a config file. Any axelix.master.* property on this page can therefore live in Vault. The usual split is to store the sensitive subset there — say, axelix.master.database.password and axelix.master.auth.jwt.signing-key — and keep everything else in an ordinary config file.

The feature is off by default. Turn it on with axelix.master.external-config.spring-cloud-vault.enabled=true and point Master at your Vault server with the uri property. With the default settings Master reads the secret stored at secret/axelix: the secret KV mount plus Master's own application name. Version 2 of the KV engine is expected — the default for any mount created by a current Vault. Seeding a configuration value therefore looks like this:

vault kv put secret/axelix \
  axelix.master.database.password=a-strong-password \
  axelix.master.auth.jwt.signing-key=cSuGCTNSJUW7yobufJTZ4C7BScamq2Yz
PropertyDefaultDescription
axelix.master.external-config.spring-cloud-vault.enabledfalseLoad configuration from HashiCorp Vault on startup.
axelix.master.external-config.spring-cloud-vault.uriemptyBase URL of the Vault server, for example http://vault:8200. Required when enabled=true.
axelix.master.external-config.spring-cloud-vault.authenticationTOKENAuthentication method. TOKEN, APPROLE, and KUBERNETES are covered below.
axelix.master.external-config.spring-cloud-vault.fail-fasttrueRefuse to start when Vault cannot be reached, instead of booting with incomplete configuration.
axelix.master.external-config.spring-cloud-vault.kv.enabledtrueRead secrets from the KV secrets engine.
axelix.master.external-config.spring-cloud-vault.kv.backendsecretMount path of the KV secrets engine.
axelix.master.external-config.spring-cloud-vault.kv.application-nameaxelixName of the secret Master reads under the mount — secret/axelix with the defaults.
axelix.master.external-config.spring-cloud-vault.kv.default-contextapplicationA shared secret read in addition to the application one (secret/application with the defaults), for settings common to several applications.
axelix.master.external-config.spring-cloud-vault.kv.profile-separator/Separator between the application name and a Spring profile in profile-specific secret paths.

Under the hood Master acts as a Spring Cloud Vault client. As a result, the whole Spring Cloud Vault connection surface is available under the same axelix.master.external-config.spring-cloud-vault.* prefix: TLS trust material under ssl.*, namespace for Vault Enterprise, connection and read timeouts, and further authentication methods beyond the three below. The table above lists only the properties most setups touch.

Vault is read once, at startup. A changed secret takes effect on the next Master restart, and only the static KV engine is consulted — dynamic secrets engines, such as short-lived database credentials, are not part of this feature.

Authenticating with a static token

TOKEN is the default method, so supplying a Vault token is all it takes. This is the quickest way to try the feature on a developer machine. A long-lived static token, however, is exactly the kind of credential Vault exists to eliminate, so in production prefer AppRole or Kubernetes below.

PropertyDefaultDescription
axelix.master.external-config.spring-cloud-vault.tokenemptyStatic Vault token Master authenticates with. Required for TOKEN.
java \
  -Daxelix.master.external-config.spring-cloud-vault.enabled=true \
  -Daxelix.master.external-config.spring-cloud-vault.uri=http://localhost:8200 \
  -Daxelix.master.external-config.spring-cloud-vault.token=hvs.6j4cuewowBGit65rheNoceI7 \
  -jar master.jar

Authenticating with AppRole

AppRole is the machine-to-machine method for environments without a platform identity: virtual machines, bare metal, Docker Compose. Master presents a role-id/secret-id pair, and Vault exchanges it for a short-lived token scoped to the role's policy.

PropertyDefaultDescription
axelix.master.external-config.spring-cloud-vault.app-role.role-idemptyThe RoleID of the AppRole Master logs in with.
axelix.master.external-config.spring-cloud-vault.app-role.secret-idemptyThe SecretID paired with role-id.
axelix.master.external-config.spring-cloud-vault.app-role.roleemptyRole name, optional, used for pull mode.
axelix.master.external-config.spring-cloud-vault.app-role.app-role-pathapproleMount path of the AppRole authentication backend.
java \
  -Daxelix.master.external-config.spring-cloud-vault.enabled=true \
  -Daxelix.master.external-config.spring-cloud-vault.uri=http://vault:8200 \
  -Daxelix.master.external-config.spring-cloud-vault.authentication=APPROLE \
  -Daxelix.master.external-config.spring-cloud-vault.app-role.role-id=5f3420cd-2c65-4a0e-87f4-b525b8e26a54 \
  -Daxelix.master.external-config.spring-cloud-vault.app-role.secret-id=f8e77c67-40b6-49aa-a548-e50c62a765b3 \
  -jar master.jar

The secret-id is itself a secret, so on container platforms pass it as an environment variable (AXELIX_MASTER_EXTERNALCONFIG_SPRINGCLOUDVAULT_APPROLE_SECRETID) rather than a -D flag.

Authenticating with Kubernetes

On Kubernetes, the Kubernetes auth method is the setup we recommend: Master authenticates with the service-account token its pod already carries, so no static credential exists in the first place. On the Vault side, create a role in the Kubernetes auth backend and bind it to Master's service account. On the Master side, name that role:

PropertyDefaultDescription
axelix.master.external-config.spring-cloud-vault.kubernetes.roleemptyVault role the login is attempted against. Required.
axelix.master.external-config.spring-cloud-vault.kubernetes.kubernetes-pathkubernetesMount path of the Kubernetes authentication backend.
axelix.master.external-config.spring-cloud-vault.kubernetes.service-account-token-file/var/run/secrets/kubernetes.io/serviceaccount/tokenPath to the pod's service-account token file.
env:
  - name: AXELIX_MASTER_EXTERNALCONFIG_SPRINGCLOUDVAULT_ENABLED
    value: "true"
  - name: AXELIX_MASTER_EXTERNALCONFIG_SPRINGCLOUDVAULT_URI
    value: "http://vault.vault.svc:8200"
  - name: AXELIX_MASTER_EXTERNALCONFIG_SPRINGCLOUDVAULT_AUTHENTICATION
    value: "KUBERNETES"
  - name: AXELIX_MASTER_EXTERNALCONFIG_SPRINGCLOUDVAULT_KUBERNETES_ROLE
    value: "axelix-master"

Like the sibling sections above, all of these properties decide where configuration is read from, so they must be known before that reading starts: supply them as JVM system properties or environment variables, never as keys inside a Vault secret.

When the feature is enabled, Master starts in fail-fast mode. If Vault cannot be reached at startup, Master refuses to boot rather than starting with incomplete configuration. While the feature stays off (the default), Master never contacts Vault and boots normally.

General

ReleasedAvailable since release: 1.1.0
PropertyDefaultDescription
axelix.master.environmentemptyName of the environment this Master instance runs in, for example production or staging. Currently only used to populate the ECS service.environment field when structured logging is enabled.

Database

The database engine is picked from the JDBC URL prefix. Which properties you must supply depends on the engine:

  • SQLite (jdbc:sqlite:...) — the bundled default jdbc:sqlite:axelix.db works for a first run with no extra configuration: username and password are not used because SQLite stores data in a local file. The file lives in the working directory, so a container without a mounted volume loses its data on restart, and SQLite cannot back multiple Master replicas — switch to PostgreSQL or MySQL for anything beyond a single-node trial.
  • PostgreSQL (jdbc:postgresql://...) — url, username, and password are all required. The user must have permission to create tables and apply schema migrations (Liquibase runs on every startup).
  • MySQL (jdbc:mysql://...) — same as PostgreSQL: url, username, and password are all required, and the user needs schema-migration privileges.
PropertyDefaultDescription
axelix.master.database.urljdbc:sqlite:axelix.dbJDBC URL. The prefix selects the engine: jdbc:sqlite:, jdbc:postgresql:, or jdbc:mysql:. SQLite does not survive container restarts without a mounted volume and does not support multiple Master replicas.
axelix.master.database.usernameemptyDatabase user. Required when url points to PostgreSQL or MySQL. Ignored for SQLite.
axelix.master.database.passwordemptyDatabase password. Required when url points to PostgreSQL or MySQL. Ignored for SQLite.

Authentication — JWT

PropertyDefaultDescription
axelix.master.auth.jwt.algorithm(unset) requiredSigning algorithm. One of HMAC256, HMAC384, or HMAC512. HMAC512 is the value used in the bundled local profile.
axelix.master.auth.jwt.signing-key(unset) requiredSecret used to sign and verify session tokens. Use a long random value. Rotate it to invalidate all sessions.
axelix.master.auth.jwt.lifespan12hToken lifetime as a Spring Duration (e.g. 30m, 12h, 7d).

Authentication — super-admin

PropertyDefaultDescription
axelix.master.auth.options.super-admin.credentials.usernameadminBuilt-in super-admin login. The super-admin holds every authority in the system.
axelix.master.auth.options.super-admin.credentials.passwordadminBuilt-in super-admin password. Pick a long, random value.

Authentication — local users

PropertyDefaultDescription
axelix.master.auth.options.local.enabledfalseAllow user accounts stored in Master's own database to sign in.

Authentication — OAuth2 / OIDC

How the OAuth2 / OIDC sign-in flow works end-to-end is covered on the Authentication page.

When axelix.master.auth.options.oauth2.enabled=true, the following properties become required:

  • axelix.master.auth.options.oauth2.issuer-uri
  • axelix.master.auth.options.oauth2.client-id
  • axelix.master.auth.options.oauth2.client-secret
  • axelix.master.auth.options.oauth2.base-url
PropertyDefaultDescription
axelix.master.auth.options.oauth2.enabledfalseEnable OIDC sign-in.
axelix.master.auth.options.oauth2.issuer-uri(unset) requiredOIDC issuer base URL. Master reads /.well-known/openid-configuration from here to discover endpoints. Required whenever oauth2.enabled=true.
axelix.master.auth.options.oauth2.client-id(unset) requiredClient identifier registered with the OIDC provider. Required whenever oauth2.enabled=true.
axelix.master.auth.options.oauth2.client-secret(unset) requiredClient secret registered with the OIDC provider. Required whenever oauth2.enabled=true.
axelix.master.auth.options.oauth2.base-url(unset) requiredPublic base URL of this Master instance, used to build the OAuth2 callback redirect URI. Required whenever oauth2.enabled=true.
axelix.master.auth.options.oauth2.scopesopenidSpace-separated scopes requested during the authorization code flow. openid is appended automatically if missing.
axelix.master.auth.options.oauth2.role-attribute-path(unset)JMESPath expression evaluated against the userinfo response to resolve an Axelix role. If unset, every OIDC user becomes a VIEWER.
PropertyDefaultDescription
axelix.master.auth.cookie.securefalseSet to true whenever Master is served over HTTPS so the session cookie is not sent over plain HTTP.

Discovery — auto-discovery

Auto-discovery lets Master find Spring Boot Starter–equipped applications without each one having to call in. On the schedule set by broadcast.schedule, Master queries the Kubernetes API for Services in the configured namespaces, filters them by label, and probes /actuator/axelix-metadata on each match using a JWT it issues itself with axelix.master.auth.jwt.signing-key. A successful probe registers the instance; a probe failure drops it from the list.

To turn the feature on, set axelix.master.discovery.auto.enabled=true (default false, i.e. off). Kubernetes is currently the only supported discovery platform.

When Master runs inside the cluster as a pod, the Kubernetes connection settings (kube-apiserver-url, sa-token-path, ca-cert-path) default to in-pod values and need no override, The starter side just has to share the JWT signing key — the starter exposes axelix-metadata by itself. When Master runs outside the cluster, point those three to the external API URL, a token file with the required RBAC, and the cluster CA path. Use filters.namespaces and filters.labels to scope the scan.

PropertyDefaultDescription
axelix.master.discovery.auto.enabledfalseEnable platform-driven autodiscovery of managed services.
axelix.master.discovery.auto.broadcast.schedule0 * * * * *Cron expression that triggers the discovery scan.
axelix.master.discovery.auto.kubernetes.kube-apiserver-urlhttps://${KUBERNETES_SERVICE_HOST}:${KUBERNETES_SERVICE_PORT_HTTPS}URL of the Kubernetes API server.
axelix.master.discovery.auto.kubernetes.sa-token-path/var/run/secrets/kubernetes.io/serviceaccount/tokenPath inside the Master pod where the ServiceAccount token is mounted.
axelix.master.discovery.auto.kubernetes.ca-cert-path/var/run/secrets/kubernetes.io/serviceaccount/ca.crtPath inside the Master pod where the kube-apiserver CA certificate is mounted.
axelix.master.discovery.auto.kubernetes.filters.namespacesdefaultComma-separated list of namespaces to scan.
axelix.master.discovery.auto.kubernetes.filters.labelsemptyMap of label key/value pairs a Kubernetes Service must match to be picked up by discovery.

Discovery — self-registration

Self-registration is the opposite direction of autodiscovery: Master accepts heartbeats pushed from a Spring Boot Starter–equipped application instead of scanning for it. Each heartbeat carries the instance's actuator URL, instance name, and a JWT signed with the shared axelix.master.auth.jwt.signing-key. Master stores the registration and treats it as a known instance until the heartbeats stop. If no heartbeat arrives within eviction.heartbeat-timeout, the eviction sweep (scheduled by eviction.schedule) drops the instance.

The endpoint is POST /api/internal/service/register. It is gated by the same JWT signing key set under axelix.master.auth.jwt.* — without a matching key, heartbeats are rejected.

On the Master side there are no required overrides — every property here has a working default. The feature is on by default (enabled=true), set it to false only if you want Master to refuse all heartbeats. The starter-side properties that go into the heartbeat (master-url, instance-actuator-url, instance-name) are documented in the Configuring Spring Boot Starter page.

PropertyDefaultDescription
axelix.master.discovery.self-registration.enabledtrueAccept self-registration heartbeats from Spring Boot Starter–equipped applications. Set to false to refuse them.
axelix.master.discovery.self-registration.eviction.heartbeat-timeout45sDrop a self-registered instance after this long without a heartbeat.
axelix.master.discovery.self-registration.eviction.schedule0 * * * * *Cron expression that triggers the eviction sweep.

MCP server

PropertyDefaultDescription
axelix.master.mcp-server.enabledtrueExpose the bundled Model Context Protocol server at /api/mcp.

Structured logging

ReleasedAvailable since release: 1.1.0

By default, Master logs plain text to the console (i.e. the process stdout). Set axelix.master.logging.json.enabled=true to switch to Elastic Common Schema (ECS) structured JSON logging instead — the only structured format Master supports today. ECS is a JSON-based logging format; reach for it when logs are shipped to an aggregator such as Elasticsearch or a similar ECS-aware pipeline.

PropertyDefaultDescription
axelix.master.logging.json.enabledfalseSwitch console logging from plain text to ECS-formatted structured JSON.

Metrics

Prometheus

ReleasedAvailable since release: 1.1.0

Master exposes a Prometheus scrape endpoint once you turn it on with axelix.master.metrics.prometheus.enabled=true. It responds at /api/actuator/prometheus and does not require authentication.

By default it uses the same port as the rest of Master (server.port). Set axelix.master.metrics.prometheus.port to use a different port instead, useful when you want to restrict access to scraping separately from the main UI and API.

Attach common tags to every exported metric with axelix.master.metrics.prometheus.tags.<name>=<value>, for example a deployment region or environment.

Because the endpoint is unauthenticated, anyone who can reach Master over the network can read metric values and any configured common tags. Restrict /api/actuator/prometheus to trusted Prometheus infrastructure with a network policy, ingress rule, or an equivalent perimeter control.

PropertyDefaultDescription
axelix.master.metrics.prometheus.enabledfalseExpose the Prometheus scrape endpoint at /api/actuator/prometheus.
axelix.master.metrics.prometheus.port(unset)Port for the Prometheus endpoint. Defaults to server.port.
axelix.master.metrics.prometheus.tagsemptyCommon tags applied to every exported metric.
axelix:
  master:
    metrics:
      prometheus:
        enabled: true
        tags:
          region: eu-west-1

Using a separate port instead:

axelix:
  master:
    metrics:
      prometheus:
        enabled: true
        port: 9404

OTLP

ReleasedAvailable since release: 1.1.0

Master can push its own JVM, HTTP, database-pool, and other Micrometer metrics to an OTLP-compatible collector over HTTP/protobuf. This exports metrics produced by Master itself, not metrics read from services connected through an Axelix starter. Export of metrics via OTLP is disabled by default (see properties below).

PropertyDefaultDescription
axelix.master.metrics.otlp.enabledfalseEnables periodic OTLP metrics export.
axelix.master.metrics.otlp.urlhttp://localhost:4318/v1/metricsComplete HTTP/protobuf metrics endpoint. Include the /v1/metrics path.
axelix.master.metrics.otlp.step1mInterval between exports, expressed as a Spring Duration.
axelix.master.metrics.otlp.headers.*emptyCustom request headers, for example an authorization header. Inject secret values at runtime.
axelix.master.metrics.otlp.compression-modenoneRequest compression mode: none or gzip.
axelix:
  master:
    metrics:
      otlp:
        enabled: true
        url: http://otel-collector:4318/v1/metrics
        step: 30s
        headers:
          Authorization: ${OTLP_AUTHORIZATION_HEADER}

These settings can equally be supplied as environment variables or JVM system properties — see Supplying properties for the relaxed-binding rules that map, for example, axelix.master.metrics.otlp.enabled to AXELIX_MASTER_METRICS_OTLP_ENABLED.

Prefer injecting authentication headers from your deployment platform's secret store instead of committing them to a configuration file.

Run as a JAR

At its core Axelix Master is just a JVM process, so it is shipped as the JAR, so it can be launched with the simple java -jar master.jar command (of course, its going to be a bit more complicated than that). If for any reason you do not use containerization, then this options is for you.

1. Download the JAR

Grab the Axelix Master JAR attached to the release you want from the Releases page. The published artifact already bundles the UI, so there is nothing else to build.

2. Configure and start Master

At minimum, provide a JWT signing key and an algorithm; everything else can stay on defaults for a first run. You can hand Master that configuration in any of three interchangeable ways — Axelix Master reads them all identically (see Supplying properties): as environment variables or -D JVM system properties on the command line (both shown next), or from an external config file such as application.yaml / application.properties.

Environment variables

Handy when your shell or process manager already exports the values:

export AXELIX_MASTER_AUTH_JWT_ALGORITHM=HMAC512
export AXELIX_MASTER_AUTH_JWT_SIGNINGKEY=replace-with-a-long-random-secret

java -jar master.jar

JVM system properties (-D)

Convenient for a quick one-off launch:

java \
  -Daxelix.master.auth.jwt.algorithm=HMAC512 \
  -Daxelix.master.auth.jwt.signing-key=replace-with-a-long-random-secret \
  -jar master.jar

External config file

ReleasedAvailable since release: 1.1.0

Keep the same settings in a YAML or properties file and point Master at it with axelix.master.config.location; see Pointing Master at a config file for the accepted location shapes.

master.yaml
axelix:
  master:
    auth:
      jwt:
        algorithm: HMAC512
        signing-key: replace-with-a-long-random-secret
java -Daxelix.master.config.location=file:/etc/axelix/master.yaml -jar master.jar

Master is then reachable at http://localhost:8080. Sign in with the super-admin credentials you configured.

Run with Docker

If you have containerization support, then the recommended way to install Axelix Master is via the docker image. The release pipeline publishes the image to GitHub Container Registry, so the name of the image follows the pattern ghcr.io/axelixlabs/axelix:1.0.0

The snippets below pin 1.0.0 for illustration. Check the Releases page for the latest published tag and substitute it in your own commands.

Run command

A minimal run, suitable for a first look:

docker run --rm -p 8080:8080 \
  -e AXELIX_MASTER_AUTH_JWT_ALGORITHM=HMAC512 \
  -e AXELIX_MASTER_AUTH_JWT_SIGNINGKEY=replace-with-a-long-random-secret \
  ghcr.io/axelixlabs/axelix:1.0.0

A few notes about this command:

  • The image runs as the non-root user axelix and uses an Ahead-of-Time class loading cache built at image-build time, so startup is faster than a plain JAR launch.
  • The default database is still SQLite at the working directory (/application/axelix.db). The file lives inside the container's writable layer and disappears when the container is removed. Either switch to PostgreSQL/MySQL (see the next section) or mount a volume at the SQLite path.

Ways to supply configuration

Prefer environment variables — inside a container they are the safest place for configuration, and the only option that keeps secrets out of the startup log.

Individual environment variables

Supplying configuration via individual environment variables follows the relaxed-binding rules from Supplying properties, passed with -e. This is what the run command above uses:

docker run --rm -p 8080:8080 \
  -e AXELIX_MASTER_DATABASE_URL=jdbc:postgresql://db.internal:5432/axelix \
  -e AXELIX_MASTER_DATABASE_USERNAME=axelix \
  -e AXELIX_MASTER_DATABASE_PASSWORD=... \
  -e AXELIX_MASTER_AUTH_JWT_ALGORITHM=HMAC512 \
  -e AXELIX_MASTER_AUTH_JWT_SIGNINGKEY=replace-with-a-long-random-secret \
  ghcr.io/axelixlabs/axelix:1.0.0

System Properties (-D) via JAVA_TOOL_OPTIONS

If you supply -D system properties through JAVA_TOOL_OPTIONS the JVM will apply this variable to the java command automatically, so it is a convenient hook for JVM tuning such as heap sizing.

The JVM prints the full value of JAVA_TOOL_OPTIONS to stderr on startup (Picked up JAVA_TOOL_OPTIONS: ...), so whatever you put there lands in docker logs and any log collector. Never place the JWT signing key, the super-admin password, or database credentials in it — pass those as discrete -e environment variables instead.

The example of using it can be found below:

docker run --rm -p 8080:8080 \
  -e JAVA_TOOL_OPTIONS="-Xmx512m -Daxelix.master.metrics.prometheus.enabled=true" \
  -e AXELIX_MASTER_AUTH_JWT_ALGORITHM=HMAC512 \
  -e AXELIX_MASTER_AUTH_JWT_SIGNINGKEY=replace-with-a-long-random-secret \
  ghcr.io/axelixlabs/axelix:1.0.0

External config file

ReleasedAvailable since release: 1.1.0

You can also supply configuration via an external config file. Mount a YAML or properties file into the container and point Master at it with axelix.master.config.location (see Pointing Master at a config file). Keep secrets out of a file baked into or committed alongside the image; pass those as discrete -e environment variables just as above.

master.yaml
axelix:
  master:
    metrics:
      prometheus:
        enabled: true

And then passing it as the volume to the docker command:

docker run --rm -p 8080:8080 \
  -v /host/config/master.yaml:/etc/axelix/master.yaml:ro \
  -e AXELIX_MASTER_CONFIG_LOCATION=file:/etc/axelix/master.yaml \
  -e AXELIX_MASTER_AUTH_JWT_ALGORITHM=HMAC512 \
  -e AXELIX_MASTER_AUTH_JWT_SIGNINGKEY=replace-with-a-long-random-secret \
  ghcr.io/axelixlabs/axelix:1.0.0

Run with Docker Compose

Logically, it is fully possible to run Axelix Master in the Docker Compose deployment.

The example below is a complete stack you can drop into a new docker-compose.yaml, edit, and start with docker compose up -d. The master service is pinned to 1.0.0 here — swap in the latest tag from the Releases page before shipping.

docker-compose.yaml
services:
  postgres:
    image: postgres:17-alpine
    environment:
      POSTGRES_DB: axelix
      POSTGRES_USER: ${AXELIX_DB_USERNAME:?set AXELIX_DB_USERNAME}
      POSTGRES_PASSWORD: ${AXELIX_DB_PASSWORD:?set AXELIX_DB_PASSWORD}
    volumes:
      - axelix-pgdata:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB"]
      interval: 5s
      timeout: 5s
      retries: 10

  master:
    image: ghcr.io/axelixlabs/axelix:1.0.0
    depends_on:
      postgres:
        condition: service_healthy
    ports:
      - "8080:8080"
    environment:
      AXELIX_MASTER_DATABASE_URL: jdbc:postgresql://postgres:5432/axelix
      AXELIX_MASTER_DATABASE_USERNAME: ${AXELIX_DB_USERNAME}
      AXELIX_MASTER_DATABASE_PASSWORD: ${AXELIX_DB_PASSWORD}
      AXELIX_MASTER_AUTH_JWT_ALGORITHM: HMAC512
      AXELIX_MASTER_AUTH_JWT_SIGNINGKEY: ${AXELIX_JWT_SIGNING_KEY}
      AXELIX_MASTER_AUTH_OPTIONS_SUPERADMIN_CREDENTIALS_USERNAME: ${AXELIX_ADMIN_USERNAME}
      AXELIX_MASTER_AUTH_OPTIONS_SUPERADMIN_CREDENTIALS_PASSWORD: ${AXELIX_ADMIN_PASSWORD}
      AXELIX_MASTER_AUTH_COOKIE_SECURE: "false"

volumes:
  axelix-pgdata:

Each AXELIX_MASTER_* key maps to the matching property through relaxed binding (see Supplying properties). Passing them as discrete environment variables — rather than bundling them into JAVA_TOOL_OPTIONS — keeps the signing key and passwords out of the container's startup log.

Another option is to provide the values via an .env file next to the Compose file:

.env
AXELIX_DB_USERNAME=axelix
AXELIX_DB_PASSWORD=...
AXELIX_JWT_SIGNING_KEY=...
AXELIX_ADMIN_USERNAME=admin
AXELIX_ADMIN_PASSWORD=...

AXELIX_MASTER_AUTH_COOKIE_SECURE is false in this example because the snippet exposes Master over plain HTTP for local testing. Put Master behind a TLS-terminating proxy and flip it to true for any real deployment.

Supplying configuration via a mounted file

ReleasedAvailable since release: 1.1.0

Instead of listing every non-secret setting under environment, you can mount a YAML or properties file into the master service and point Master at it with axelix.master.config.location (see Pointing Master at a config file):

master.yaml
axelix:
  master:
    database:
      url: jdbc:postgresql://postgres:5432/axelix
    auth:
      cookie:
        secure: false
docker-compose.yaml
  master:
    image: ghcr.io/axelixlabs/axelix:1.0.0
    volumes:
      - ./master.yaml:/etc/axelix/master.yaml:ro
    environment:
      AXELIX_MASTER_CONFIG_LOCATION: file:/etc/axelix/master.yaml
      AXELIX_MASTER_AUTH_JWT_ALGORITHM: HMAC512
      AXELIX_MASTER_AUTH_JWT_SIGNINGKEY: ${AXELIX_JWT_SIGNING_KEY}

Keep secrets — the JWT signing key, the super-admin password, database credentials — out of a committed file, and pass those as discrete environment variables as shown above.

Run on Kubernetes

Use this when you want Master to live in the same cluster as the services it monitors and to discover them automatically.

Axelix ships a first-party Helm chart, so you do not need to assemble manifests by hand:

A typical install boils down to:

helm repo add axelix https://axelixlabs.github.io/helm-charts
helm repo update
helm install axelix axelix/axelix \
  --namespace axelix --create-namespace \
  --values values.yaml

Put your overrides — at minimum axelix.master.auth.jwt.signing-key, the super-admin password, and the database connection — into values.yaml. The chart wires the ServiceAccount + RBAC required for in-cluster autodiscovery for you.

Expose the Service through your cluster's usual mechanism — an Ingress or a Gateway — for a quick check.

Bringing your own chart

Some teams keep their own Helm chart or raw manifests rather than the first-party chart. That is fine. There are just a couple of things that need to be emphasized.

Automatic Discovery

During auto-discovery, Axelix Master queries the K8S control plane REST API. In order to do that, the K8S ServiceAccount that is bundled into the pod must have the appropriate permissions. The official chart wires them up for you.

This matters only when axelix.master.discovery.auto.enabled=true — without auto-discovery Master needs none of the permissions below.

Here is what to keep in mind when writing custom Helm Chart:

1. Master must reach the Kubernetes API. As mentioned, in auto-discovery mode Master calls the cluster's control-plane API on the schedule set by broadcast.schedule, listing Services and the Pods behind them in each configured namespace. By default, Axelix Master reads the connection details from the standard in-pod locations:

  • The URL of the API server from KUBERNETES_SERVICE_HOST / KUBERNETES_SERVICE_PORT_HTTPS, which Kubernetes injects into every pod;
  • The ServiceAccount token is taken from /var/run/secrets/kubernetes.io/serviceaccount/token;
  • The CA certificate from /var/run/secrets/kubernetes.io/serviceaccount/ca.crt.

Those are the defaults of the kube-apiserver-url, sa-token-path, and ca-cert-path properties listed under auto-discovery. Keep the defaults, and your only job is to make the token and CA land at those paths. The next two points cover that.

2. The pod needs a ServiceAccount with its token mounted. Create a ServiceAccount, set serviceAccountName on the Deployment, and leave automountServiceAccountToken: true so the token and CA appear at the paths above. Without a mounted token Master has no credentials to present, and every scan fails with an authorization error.

3. That ServiceAccount needs RBAC to read workloads. A bare ServiceAccount can authenticate but is allowed nothing. Grant it get, list, and watch on pods, services, and endpoints in each namespace you want discovered, then bind the created Role to the ServiceAccount. The namespaces you grant must match filters.namespaces: Master scans exactly those, and a namespace it cannot read returns no instances.

Put together, the minimum for a single discovered namespace looks like this:

rbac.yaml
apiVersion: v1
kind: ServiceAccount
metadata:
  name: axelix-master
  namespace: axelix
automountServiceAccountToken: true
---
apiVersion: rbac.authorization.k8s.io/v1
kind: Role
metadata:
  name: axelix-master-role
  namespace: default          # a namespace listed in filters.namespaces
rules:
  - apiGroups: [""]
    resources: ["pods", "services", "endpoints"]
    verbs: ["get", "list", "watch"]
---
apiVersion: rbac.authorization.k8s.io/v1
kind: RoleBinding
metadata:
  name: axelix-master-role-binding
  namespace: default
subjects:
  - kind: ServiceAccount
    name: axelix-master
    namespace: axelix         # the namespace the ServiceAccount lives in
roleRef:
  kind: Role
  name: axelix-master-role
  apiGroup: rbac.authorization.k8s.io

Then point the Deployment at that ServiceAccount:

deployment.yaml
spec:
  template:
    spec:
      serviceAccountName: axelix-master
      containers:
        - name: axelix-master
          image: ghcr.io/axelixlabs/axelix:1.0.0

In K8S, a Role is a namespaced object, so repeat the Role and RoleBinding once per namespace in filters.namespaces, all referencing the same ServiceAccount. When the ServiceAccount lives in a different namespace from the workloads, the RoleBinding subject must still name the ServiceAccount's own namespace, as shown above.

The official's chart templates/rbac.yaml and templates/serviceaccount.yaml are the reference for exactly this. You may run helm template against the chart and copy the rendered ServiceAccount, Role, and RoleBinding into your own manifests if you want a starting point.

On this page