Skip to main content

Configuration reference

Lookup, not narrative. Every variable the stack reads, what it does, and what it defaults to. .env.example is the annotated template these come from.

Image pins (*_VERSION) are written by make stack VERSION=X.Y.Z from the DFE stack SSoT. Compose hard-fails on an unset pin rather than resolving latest, so run it before any compose command -- deploying.md.

Environment Variables​

The sections follow .env.example: stack-wide settings first, then one per component in docker-compose.yml order.

Image Registry​

VariableUseDefault
DFE_STACK_VERIFYfalse pulls the stack manifest without checking its signaturetrue
DFE_STACK_VERSIONThe stack make stack last pinned, or latest or rc to follow the newestnone, make stack sets it
DOCKER_DEFAULT_PLATFORMImage architecture to pull from docker (if not wanting automatic determination)-
IMAGE_REGISTRYOCI registry hosting published dfe-* imagesghcr.io/hyperi-io

Container names​

VariableUseDefault
DFE_CONTAINER_PREFIXPrepended to every container name, for a second stack on this host. It changes names only, so set COMPOSE_PROJECT_NAME and new host ports with itempty

Profile selection​

VariableUseDefault
DFE_PROFILEOverride active profile from service_profiles.yaml-
DFE_CLICKHOUSE_ENABLEDToggle to start the bundled ClickHouse. false with an external CLICKHOUSE_HOST, which alone does not skip itunset, the profile decides
DFE_CORE_ENABLEDToggle to turn on core componentstrue
DFE_HYPERDX_ENABLEDToggle to start dfe-hyperdx + its proxy + DocumentDB. Ignored with DFE_CORE_ENABLED=false, since dfe-hyperdx signs callers in with the engine's tokenunset, the profile decides
DFE_OTEL_ENABLEDToggle to start the collectorunset, the profile decides
KAFBAT_ENABLEDToggle to turn on Kafbattrue

Host exposure​

Which interface each published port binds. Ports are grouped by audience -- operating.md has the per-port table and the UI classes.

VariableUseDefault
DFE_INGRESS_BIND_HOSTInterface the receiver ingest ports bind0.0.0.0
DFE_BIND_HOSTInterface the datastore, broker, metrics and internal gRPC ports bind127.0.0.1
DFE_BIND_SCOPEWhere every web UI publishes (localhost|all); make resolves it to DFE_UI_BIND_HOST, and all requires DFE_EXTERNAL_ORIGINlocalhost
DFE_INFRA_UIS_EXTERNALKill switch -- false unpublishes every infra-class UI and beats their own flagstrue
DFE_UI_EXTERNALPublish the DFE UI (dfe-proxy :3000); product class, kill switch never covers ittrue
DFE_ENGINE_API_EXTERNALPublish the engine API (:8003); product classtrue
DFE_KAFBAT_UI_EXTERNALPublish Kafbat (:8081); infra classtrue
DFE_HYPERDX_UI_EXTERNALPublish dfe-hyperdx (:8090 and its API :8000); infra classtrue

External origin​

The scheme and host a browser reaches this box on: scheme and host only, no port and no trailing slash. Compose appends the port itself (DFE_UI_PORT for the console), so make refuses a value with a port, a path, a query, no scheme or a character no host holds, such as ;. The same holds for DFE_HYPERDX_APP_URL and DFE_OAUTH2_PROXY_EXTERNAL_ORIGIN. Four surfaces build absolute URLs from the origin, and each is wrong when it says localhost and the browser did not: the DFE UI's post-logout redirect, the oauth2-proxy callbacks registered with the IdP, dfe-hyperdx's link-backs into the DFE UI, and the frame-ancestors policy the proxy serves dfe-hyperdx under. The first three send the browser somewhere it cannot follow; the fourth blocks the console's embedded views outright.

It defaults to http://localhost, and nothing infers the real one -- so reaching the stack on a hostname without setting this is what sends every logout to http://localhost:3000. Set it on any deployment a browser reaches by anything other than localhost; DFE_BIND_SCOPE=all requires it, and make stops the start goals until it is set.

With console TLS on it must start with https://.

The ancestors list keeps http://localhost and http://127.0.0.1 at the UI port alongside it, so setting an origin never takes the console away from an operator working on the box itself.

VariableUseDefault
DFE_EXTERNAL_ORIGINBrowser-facing scheme and host only, no port, for every absolute URL the stack buildshttp://localhost

Console TLS (opt-in)​

DFE_PROXY_TLS=true serves the console (DFE_UI_PORT), the dfe-hyperdx embed (DFE_HYPERDX_EMBED_PORT) and both dfe-hyperdx origins (DFE_HYPERDX_APP_PORT, DFE_HYPERDX_API_PORT) over TLS 1.2 or 1.3 with one certificate. Key exchange prefers hybrid post-quantum X25519MLKEM768, then P-384. TLS 1.2 offers AEAD suites only, AES-256-GCM first. OIDC sign-in needs it on any hostname but localhost: outside a dev environment the engine marks its OIDC state and token cookies Secure and a browser drops a Secure cookie sent over plain http.

make certs mints the pair into DFE_PROXY_CERT_DIR: a P-384 console.crt and 0600 console.key for the host DFE_EXTERNAL_ORIGIN names plus localhost and 127.0.0.1, along with the ca.crt that signed them. It deletes the CA's private key once it has signed, so trusting ca.crt in the browser or OS trusts that one certificate and nothing else. It never replaces a file already there. To renew or change the hostname, delete the three files, run it again and trust the new ca.crt. Both proxies run as the user who runs make under TLS, so run it as that user.

Each machine whose browser opens the console trusts ca.crt once, as a certificate authority. Copy ca.crt there and nothing else: console.key stays on the stack's host. The hostname must resolve on that machine too, through DNS or /etc/hosts.

  • Firefox keeps its own trust store: Settings, Privacy & Security, View Certificates, the Authorities tab, Import, then tick "Trust this CA to identify websites".
  • Chrome and Edge: open Manage certificates in Settings and import ca.crt as a trusted authority. The menu names differ between versions and platforms.
  • macOS, which Safari uses: sudo security add-trusted-cert -d -r trustRoot -k /Library/Keychains/System.keychain ca.crt.
  • Windows, from an administrator prompt: certutil -addstore -f Root ca.crt.
  • Debian and Ubuntu system tools such as curl: copy it to /usr/local/share/ca-certificates/dfe-local-ca.crt, then run sudo update-ca-certificates.

A CA-issued certificate goes in the same two files instead, with the full chain in console.crt. Copy real files rather than linking them: a symlink, certbot's live/ layout included, dangles inside the container, so cp -L the pair into place.

Set DFE_EXTERNAL_ORIGIN=https://dfe.example.com and make DFE_HYPERDX_APP_URL an https:// origin too if you set it. Register the engine's callback with the IdP as https://<hostname>:<DFE_UI_PORT>/api/v1/auth/oidc/<provider>/callback, without the port when DFE_UI_PORT is 443.

make refuses to start the stack until both files are real files the make user can read, openssl accepts them as a matching certificate and unencrypted key, both origins start with https://, DFE_NETWORK_SUBNET overlaps no other Docker network and the auth profile is off. oauth2-proxy serves no TLS here, so its callbacks could not answer an https:// origin. Both Envoy proxies run as the make user, because the image's own uid 101 cannot read the 0600 key.

dfe-proxy's two ports serve TLS only. The dfe-hyperdx ports answer TLS and plain HTTP on the same port, because the engine's admin-link probe, make post and the oauth2-proxy upstreams dial them over http on the compose network.

The engine builds the callback URL from X-Forwarded-Proto, which it believes from dfe-proxy alone. TLS therefore pins the compose network to DFE_NETWORK_SUBNET, gives dfe-proxy its last host address and allocates every other container from the first half, so no other container and not the bridge gateway can hold the address the engine trusts. The default 10.207.0.0/24 sits outside Docker's stock address pools and the fleet's 172.16.0.0/12 pool. Move it when a second stack on the same host also runs TLS. Move it too when make reports an overlap or the range clashes with a LAN or VPN route. It must be a private (RFC 1918) network from /16 to /24.

Turning DFE_PROXY_TLS on or off on a running stack needs make down first, because Docker cannot re-address a live network. make up, make ci and make dev stop the stack themselves; make apply and make infra refuse until you do.

make test-e2e and make test-resilience run under the dial too, with the same prechecks as a start. Each test's stack chains the fragments make ci does, so the exposure opt-outs and container logs apply to it as well. The suite probes the console at https://<DFE_EXTERNAL_ORIGIN host>:<DFE_UI_PORT> and verifies its certificate by chain and by name, never with verification off.

A certificate from a public CA verifies against the system trust store. For a private CA, set DFE_PROXY_CA_BUNDLE to a PEM file holding the CA chain that signed console.crt, which after make certs is its ca.crt. Chrome takes no CA bundle, so the browser steps are pinned to the public key of the certificate that check has just verified.

VariableUseDefault
DFE_PROXY_TLSServe the console and dfe-hyperdx over TLS (true|false)false
DFE_PROXY_CERT_DIRDirectory holding console.crt and console.key; a path starting /, ./ or .././certs
DFE_NETWORK_SUBNETCompose network under TLS, private and /16 to /24; dfe-proxy takes its last host10.207.0.0/24
DFE_PROXY_CA_BUNDLEPEM CA chain the e2e suites verify the console certificate againstunset: the system trust store

Resource limits​

Ceilings, not reservations, sized for the default profile on a 16 GB box. make limits prints the resolved values.

VariableUseDefault
DFE_BROKER_CPUSCPU ceiling of the broker1.5
DFE_BROKER_MEMORYMemory ceiling of the broker. Keep it above REDPANDA_MEMORY or the broker is OOM-killed2560M
DFE_CLICKHOUSE_CPUSCPU ceiling of ClickHouse2.0
DFE_CLICKHOUSE_MEMORYMemory ceiling of ClickHouse3G
DFE_HYPERDX_MEMORYMemory ceiling of dfe-hyperdx, which holds about 820 MiB for its first 100 s, over the service tier's2G
DFE_SERVICE_CPUSCPU ceiling of the DFE components, the UI, Kafbat and dfe-hyperdx. Below 2.0 a scalo service gets one worker thread and stops answering its health check2.0
DFE_SERVICE_MEMORYMemory ceiling of the same service tier. kafka-ui's JVM sizes its heap from it768M
DFE_SIDECAR_CPUSCPU ceiling of the one-shots, both Envoy proxies, otel-collector and the oauth2-proxy gates0.5
DFE_SIDECAR_MEMORYMemory ceiling of the same sidecars512M

All DFE apps​

VariableUseDefault
LOG_LEVELLog level (trace|debug|info|warn|error)info
LOG_FORMATLog formattext
DFE_OTEL_EXPORTER_ENDPOINTWhere services push; empty exports nothingempty (the profile points it at the bundled collector)

The DFE Rust images ship bash and no curl or wget, so their compose healthchecks are the image's own HEALTHCHECK: a GET over bash's /dev/tcp. check-compose rejects curl or wget in a DFE service whose image lacks it. Each app's stop_grace_period is its deployment contract's termination_grace_seconds, 45 s and 90 s for the Vector transform, because Docker's 10 s default kills a drain mid-flush.

Release check​

Every DFE app asks releases.hyperi.io at startup whether a newer version exists. It sends one POST of the product, its version, the OS, the architecture and a one-way install id, and a failure costs a warning line. It is on by default, and two lines in .env change that for every app at once.

VariableUseDefault
DFE_VERSION_CHECK_ENABLEDfalse stops the check in every app, so none of them phones outtrue
DFE_VERSION_CHECK_SEND_INSTANCE_IDfalse keeps the check and sends no install idtrue

Compose maps them onto scalo's version_check.enabled and version_check.send_instance_id keys, in the env form scalo's cascade nests with. Each app reads those under its own prefix, so compose does the spelling: DFE_ARCHIVER_, DFE_FETCHER_, DFE_LOADER_ and DFE_RECEIVER_ for those four, DFE_TRANSFORM_ELASTIC_, DFE_TRANSFORM_VECTOR_ and DFE_TRANSFORM_VRL_ for the transforms and no prefix for dfe-engine. dfe-ui and the hunt runner start no check. A compose environment: entry beats env/<app>.env, so set the dial in .env. The third-party images keep their own telemetry settings, which this dial does not reach.

Dev builds​

VariableUseDefault
DFE_SRC_ROOTDirectory holding your local dfe-* checkouts. Set, the build (and LIVE_UI=1's next dev) uses those instead of the git cacheunset (git cache)
DFE_SRC_REMOTEGit base URL the managed cache clones component repos fromhttps://github.com/hyperi-io
DFE_SRC_REFBranch, tag or commit the managed cache buildsmain
DFE_SRC_CACHELocation of the managed source cache~/.cache/dfe-docker/src

Power-on self test​

VariableUseDefault
DFE_POST_DATABASEDatabase the traced test events land indfe
DFE_POST_ENABLEDRun the self test after make dev and make ci. make post runs it by handtrue
DFE_POST_HOSTHost the test reaches the published ports onlocalhost
DFE_POST_TABLETable the traced test events land inmain

OIDC test-fixture login​

VariableUseDefault
DFE_OIDC_FIXTURE_USERThrowaway login the console specs sign in with through an external provider. Nothing in the stack reads it. make creds prints itdfe-test@dfe-oidc.test
DFE_OIDC_FIXTURE_PASSWORDThat login's password. The specs skip a provider without one-
DFE_OIDC_<PROVIDER>_FIXTURE_USER, _PASSWORDA per-provider pair (DEX, ENTRA, GOOGLE, OKTA), which wins over the generic one-

ClickHouse​

VariableUseDefault
CLICKHOUSE_VERSIONVersion of ClickHouse to usenone -- make stack pins it from the DFE stack SSoT; unset is a hard-fail
CLICKHOUSE_HOSTExternal ClickHouse host (skips Docker container)clickhouse
CLICKHOUSE_HTTP_PORTHost publish of the bundled ClickHouse's HTTP port; services in the stack dial 8123 whatever it says, so it is safe to move8123
CLICKHOUSE_NATIVE_PORTHost publish of the bundled ClickHouse's native port; services in the stack dial 9000 whatever it says, so it is safe to move9000
CLICKHOUSE_EXTERNAL_HTTP_PORTHTTP port dfe-engine, dfe-hunt-runner, dfe-loader and the connection the engine hands dfe-hyperdx dial on an external CLICKHOUSE_HOST. Leave unset for the bundled container8123
CLICKHOUSE_EXTERNAL_NATIVE_PORTNative port the collector dials on an external CLICKHOUSE_HOST. Leave unset for the bundled container9000
CLICKHOUSE_SECURETLS to an external CLICKHOUSE_HOST for dfe-engine, dfe-hunt-runner and the connection the engine hands dfe-hyperdx (https://), on true, 1 or yes. The collector and the loader do not read itfalse
CLICKHOUSE_DBClickHouse initialisation databasedefault
CLICKHOUSE_USERNAMEClickHouse admin username, which only dfe-engine connects asdefault
CLICKHOUSE_PASSWORDClickHouse admin password, which only dfe-engine connects with-
CLICKHOUSE_KEEPER_PASSWORDThe bundled ClickHouse's embedded Keeper identity, which every znode is created under and which is fixed for the life of the clickhouse-data volume. Compose refuses to start without itgenerated

Kafka​

VariableUseDefault
DFE_KAFKA_BOOTSTRAP_SERVERSThe broker dfe-engine creates topics on and dfe-archiver, dfe-fetcher, dfe-loader, dfe-receiver, dfe-transform-elastic, dfe-transform-vector and dfe-transform-vrl read or write. Kafbat browses it too. Set it for an external brokerkafka:9092, the in-stack broker
KAFKA_ADVERTISED_HOSTName the host listener advertises to clients off this box. Set it with DFE_BIND_HOST=0.0.0.0localhost
KAFKA_BACKENDKafka backend for kafka transport profilesredpanda
KAFKA_PLAINTEXT_HOST_PORTKafka plaintext host port (host-facing)19092
APACHE_KAFKA_VERSIONVersion of Apache Kafka to usenone -- make stack pins it from the DFE stack SSoT; unset is a hard-fail
REDPANDA_VERSIONVersion of Redpanda to usenone -- make stack pins it from the DFE stack SSoT; unset is a hard-fail
REDPANDA_MEMORYMemory for the Redpanda broker (Seastar reserves this up front); redpanda/start.sh lowers it to what the container limit leaves after the host's vm.min_free_kbytes1G
REDPANDA_MODEdev-container relaxes fsync to fit a 4 GB CI runner. A deploy sets production with real REDPANDA_SMP and REDPANDA_MEMORYdev-container
REDPANDA_SMPCores the broker uses1

Kafka UI (Kafbat)​

VariableUseDefault
KAFBAT_VERSIONVersion of Kafbat to usenone -- make stack pins it from the DFE stack SSoT; unset is a hard-fail
KAFBAT_PORTKafbat port8081

DFE Archiver​

Only single-kafka runs dfe-archiver, so archiving needs a Kafka profile (profiles.md).

VariableUseDefault
DFE_ARCHIVER_VERSIONVersion of dfe-archiver to usenone -- make stack pins it; unset is a hard-fail
DFE_ARCHIVER_PROMETHEUS_PORTArchiver Prometheus port9093

DFE Fetcher​

VariableUseDefault
DFE_FETCHER_VERSIONVersion of dfe-fetcher to usenone -- make stack pins it; unset is a hard-fail
AWS_ACCESS_KEY_IDAWS access key. Goes in env/fetcher.env, NOT .env -- the fetcher config reads it via env:-
AWS_SECRET_ACCESS_KEYAWS secret key. Same file as above-
AZURE_CLIENT_IDIn env/fetcher.env. Client ID an Azure source names with env:AZURE_CLIENT_ID-
AZURE_CLIENT_SECRETIn env/fetcher.env. That client's secret, named the same way-
AZURE_SUBSCRIPTION_IDIn env/fetcher.env. Subscription an Azure source reads-
AZURE_TENANT_IDIn env/fetcher.env. Tenant the client belongs to-

The AWS region and poll interval are not environment variables: they are literals in each source's fetcher config (region: us-west-2 and interval_secs in resources/seed/sources/aws-*.yaml, 30 seconds and 300 for GuardDuty, whose every poll stores all its findings again), because only the two credential fields are env:-interpolated.

DFE Loader​

VariableUseDefault
DFE_LOADER_VERSIONVersion of dfe-loader to usenone -- make stack pins it; unset is a hard-fail
DFE_LOADER_PROMETHEUS_PORTLoader Prometheus port9091
DFE_LOADER_CLICKHOUSE_PASSWORDPassword of dfe_loader, the loader's own ClickHouse user, which the engine creates on itgenerated

DFE Receiver​

VariableUseDefault
DFE_RECEIVER_VERSIONVersion of dfe-receiver to usenone -- make stack pins it; unset is a hard-fail
DFE_RECEIVER_BEATS_PORTHost port for the receiver's Beats listener (container port 5044)unset, unpublished
DFE_RECEIVER_GRPC_PORTHost port for the receiver's Vector gRPC listener (container port 6000)unset, unpublished
DFE_RECEIVER_HEC_PORTHost port for the receiver's HEC listener (container port 8088)unset, unpublished
DFE_RECEIVER_HTTP_PORTReceiver HTTP port8080
DFE_RECEIVER_MAX_BODY_SIZELargest record the receiver accepts in bytes, under every broker's 8 MiB message limit8000000
DFE_RECEIVER_OTLP_GRPC_PORTHost port for the receiver's OTLP gRPC listener (container port 4317)unset, unpublished
DFE_RECEIVER_OTLP_HTTP_PORTHost port for the receiver's OTLP HTTP listener (container port 4318)unset, unpublished
DFE_RECEIVER_PROMETHEUS_PORTReceiver Prometheus port9090

Setting a listener's port key publishes it through a fragment make writes, so a raw docker compose outside make does not publish it. The listener still has to be enabled in the receiver's config, such as DFE_RECEIVER_GRPC__ENABLED=true in env/receiver.env.

make also sets DFE_RECEIVER_DLQ_MODE from the profile: file_only on a direct gRPC profile and empty on Kafka, which keeps the receiver's cascade default.

DFE Transform Elastic​

VariableUseDefault
DFE_TRANSFORM_ELASTIC_VERSIONVersion of dfe-transform-elastic to usenone -- make stack pins it; unset is a hard-fail

DFE Transform Vector​

VariableUseDefault
DFE_TRANSFORM_VECTOR_VERSIONVersion of dfe-transform-vector to usenone -- make stack pins it; unset is a hard-fail

DFE Transform VRL​

VariableUseDefault
DFE_TRANSFORM_VRL_VERSIONVersion of dfe-transform-vrl to usenone -- make stack pins it; unset is a hard-fail

DFE Engine​

The engine reads its settings under DFE_ENGINE_, so env/engine.env takes those names and any other is ignored. DFE_GOOGLE_SA_JSON and DFE_OIDC_* are the exception, because a provider file names them.

VariableUseDefault
DFE_ENGINE_VERSIONVersion of dfe-engine to use, which also pins the dfe-schemas it carriesnone, make stack pins it and unset is a hard-fail
DFE_ENGINE_PORTPort used by dfe-engine8003
DFE_ENGINE_API_JWT_SECRETSigns the OIDC sign-in session. dfe-hunt-runner gets it only to clear a startup checkgenerated by make init
DFE_ENGINE_AUTH_BREAKGLASS_PASSWORDIn env/engine.env. The breakglass recovery login, hashed into the deploy repo. make creds hands it overgenerated by make init
DFE_ENGINE_AUTH_LOCAL_ADMIN_NAMEIn env/engine.env. The everyday admin's login nameadmin
DFE_ENGINE_AUTH_LOCAL_ADMIN_PASSWORDIn env/engine.env. That login's password. make creds hands it overgenerated by make init
DFE_ENGINE_GITOPS_ENABLEDThe deploy repo. Off keeps everything as plain files, with no history, no policies and no break-glass login, as its hash lives in the repo. Off is the engine's own defaulttrue
DFE_ENGINE_GITOPS_FORGE_API_BASE, DFE_ENGINE_GITOPS_FORGE_PROVIDERIn env/engine.env. The git server's API, so team mode opens the PR itself. Without them a change waits on its branch for someone to open oneempty
DFE_ENGINE_GITOPS_MODEsolo commits a change straight to the deploy repo's main. team puts it on a review branch, live once merged, and needs DFE_ENGINE_GITOPS_REPO_URLsolo
DFE_ENGINE_GITOPS_REPO_URL, DFE_ENGINE_GITOPS_TOKEN, DFE_ENGINE_GITOPS_USERNAMEIn env/engine.env. The git server holding the deploy repo, which must already have a main branch. Empty keeps the repo localempty
DFE_GOOGLE_SA_JSONIn env/engine.env. Google service-account JSON, named by a provider file's groups.service_account_json_env-
DFE_OIDC_<PROVIDER>_CLIENT_ID, _CLIENT_SECRETIn env/engine.env. The engine's own OIDC client per provider (DEX, ENTRA, GOOGLE, OKTA), named by its file in /app/config/auth/oidc-providers-
SSL_CERT_FILEIn env/engine.env. A full CA bundle, for an IdP on a private CAthe image's bundle
DFE_ENGINE_CLICKHOUSE_DEFAULT_TTL_DAYSIn env/engine.env. Days every time-series table keeps rows, the OTel tables included. 0 disables the default TTL and a source or dfe-schemas TTL overrides it90
DFE_ENGINE_API_METRICS_BACKENDIn env/engine.env. Metrics backend of dfe-engine. opentelemetry is dual (push + /metrics)opentelemetry

Compose sets these on the engine itself:

VariableWhy
DFE_ENGINE_ADMIN_LINKSThe console's links to the published infra UIs
DFE_ENGINE_APP_CONTRACT_DIRWhere the contracts land, empty where no config renders
DFE_ENGINE_DEPLOYMENT_APP_CONFIG_BASE_DIR, DFE_ENGINE_DEPLOYMENT_APP_CONFIG_DIRThe ConfigMap stand-in. Empty turns the writer off
DFE_ENGINE_DEPLOYMENT_APP_ENV_DIRWhere the custom.env files land, on the read-write ./env mount
DFE_ENGINE_DEPLOYMENT_TARGETdocker, which names make creds on the login page
DFE_ENGINE_ENVproduction refuses to start without real auth. .env can set it
DFE_ENGINE_GITOPS_LOCAL_PATHThe deploy repo, on the config volume
DFE_ENGINE_HUNTS_DIR, DFE_ENGINE_HUNTS_RULES_DIRHunts and rules, in the deploy repo's tree with or without a repo, because that is the part dfe-hunt-runner mounts
DFE_ENGINE_HYPERDX_BASE_URL, DFE_ENGINE_HYPERDX_ENABLEDFollow the footprint, like the dfe-hyperdx containers
DFE_ENGINE_PROFILEThe tier name, which seeds its default app instances
DFE_ENGINE_SECRETS_PATHOn the config volume, so the JWT key survives a recreate
DFE_ENGINE_STACK_VERSION, DFE_ENGINE_UI_VERSIONThe certified stack and the console version, for the console footer. The second is the UI pin without its digest
DFE_ENGINE_TRANSPORT_BUS_PRESENT, DFE_ENGINE_TRANSPORT_DEFAULTFrom the profile. Their defaults are slim-grpc's

The engine is the only writer of dfe-app-config, which every app mounts read-only, and each app's base config mounts under the instance overlay. It waits on the contracts, where a failed emit only warns, and on the broker because it creates the topics. Its 540 s start_period covers a dead engine's lease (2 x 180 s) plus table creation.

DFE Hunt Runner​

VariableUseDefault
DFE_HUNT_RUNNER_CLICKHOUSE_PASSWORDPassword of dfe_hunt_runner, the hunt runner's own ClickHouse user; the engine creates the user on it and the runner connects with itgenerated

The runner is a console script in the engine's image. It runs SQL built from rule text, so it connects as its own user and reads no env/*.env, which keeps the engine's logins out of its environment. It reads hunts and rules from the deploy repo the engine governs and rereads them every poll (--reload-every 1, against a default of every 20 ticks). It has no HTTP listener, so its healthcheck is off and its liveness is its ClickHouse lease.

DFE UI​

VariableUseDefault
DFE_UI_VERSIONVersion of dfe-ui to usenone -- make stack pins it; unset is a hard-fail
DFE_UI_PORTPort used by dfe-ui3000
NEXTAUTH_SECRETIn env/ui.env. Signs the console's sessiongenerated by make init

Compose points the console at the engine with INTERNAL_API_URL, for its server-side calls. NEXTAUTH_URL follows DFE_EXTERNAL_ORIGIN and DFE_UI_PORT, because unset, logout lands on http://localhost:3000. The console has no gRPC exporter, so it pushes to the collector's HTTP port. Its image ships no curl, so its healthcheck is node's fetch on /livez.

DFE Proxy​

VariableUseDefault
DFE_PROXY_VERSIONThe envoyproxy/envoy tag dfe-proxy runsnone, make stack pins it

dfe-proxy re-serves dfe-hyperdx on :8091 with a frame-ancestors header so the console can frame it. Compose fills that header's DFE_HYPERDX_EMBED_ANCESTOR with the origins allowed, loopback included.

OTel collector​

Every profile declares otel: true, which starts a collector that takes the stack's own telemetry over OTLP and writes the dfe.otel_* tables dfe-hyperdx reads. DFE_OTEL_ENABLED=false turns it off. The only scrape is the collector reading its own metrics on 127.0.0.1:8888 and its OTLP ports stay on the Compose network. Full picture, including which services report today: observability.md.

VariableUseDefault
DFE_OTEL_COLLECTOR_VERSIONVersion of otel/opentelemetry-collector-contribnone -- make stack pins it; unset is a hard-fail
DFE_OTEL_DATABASEClickHouse database the collector writesdfe
DFE_OTEL_HEALTH_PORTCollector health_check host port13133
DFE_OTEL_COLLECTOR_LOG_LEVELCollector's own log levelwarn
DFE_OTEL_FLUENT_PORTCollector fluent_forward host port, where the daemon sends container stdout24224
DFE_CONTAINER_LOGS_ENABLEDShip DFE container stdout; false puts every service back on json-filetrue
DFE_CONTAINER_LOG_ADDRESSWhere the DOCKER DAEMON sends ittcp://<DFE_BIND_HOST>:<DFE_OTEL_FLUENT_PORT>
DFE_OTEL_WRITER_CLICKHOUSE_PASSWORDPassword of dfe_otel_writer, the collector's own ClickHouse user, which the engine creates on itgenerated

The collector runs with create_schema=false, so it waits on the engine, which makes the tables. /proc mounts at /hostfs/proc for host_metrics' CPU scraper. make sets DFE_USAGE_CPU_LIMITS and DFE_USAGE_NODE_NAME for the usage rollup.

dfe-hyperdx​

Every profile declares hyperdx: true and DFE_HYPERDX_ENABLED=false turns it off. It starts dfe-hyperdx (API + App), the dfe-hyperdx-proxy that fronts both origins, a one-shot dfe-dashboards that copies the engine's shipped dashboards into a volume, plus hyperdx-documentdb, sharing the always-on ClickHouse. dfe-dashboards runs the engine image's dashboards module as root, never dfe-engine run, so it needs no config, secrets or database.

hyperdx-documentdb is PostgreSQL with the DocumentDB extensions and the Mongo-wire gateway dfe-hyperdx talks to, in one container, with its usage telemetry to a third party switched off. The gateway serves TLS only, on a certificate the one-shot hyperdx-documentdb-tls mints into a volume on first start and renews once it has 30 days left. It runs as root, since a fresh volume lands root-owned, and leaves the pair to uid 1000, which both documentdb-local and dfe-hyperdx run as. dfe-hyperdx gets only the CA that signed it, verifies the gateway against it and logs in with SCRAM-SHA-256.

dfe-hyperdx runs in oidc-proxy mode, as it does on Kubernetes: it verifies the engine's ES384 token against the engine's JWKS. A caller carrying none is unauthenticated. Signing in to the console is what signs a browser in here: dfe-ui mirrors the session into a dfe_token cookie and cookies ignore ports. The engine identifies with a service token of its own, which is how a source added through the console reaches every dfe-hyperdx team. That token's team, DFE_HYPERDX_TEAM, holds no connection and no sources, because dfe-hyperdx seeds a team only from a person's login, never from the service token. Every other team gets its connection and sources on its members' first login, from the engine's answer for that member: main and hunts everywhere, plus the otel and clickhouse_system sources on a platform team. Each team also gets the shipped dashboards those sources can render. From hyperi-io/dfe-hyperdx#106 on, a login the engine gives no connection is retried on a later request.

The same toggle points the engine at dfe-hyperdx: with it on, the engine receives DFE_ENGINE_HYPERDX_ENABLED=true and DFE_ENGINE_HYPERDX_BASE_URL=http://dfe-hyperdx:8000, so its /api/v1/hyperdx/* surface answers instead of returning 503 hyperdx_absent.

VariableUseDefault
DFE_HYPERDX_VERSIONVersion of the dfe-hyperdx fork image to usepinned by make stack from the SSoT (fail-loud, like every image)
DFE_HYPERDX_API_PORTdfe-hyperdx API host port, on dfe-hyperdx-proxy8000
DFE_HYPERDX_APP_PORTdfe-hyperdx App UI host port, on dfe-hyperdx-proxy8090
DFE_HYPERDX_APP_URLScheme and host only, no port, the browser uses to reach dfe-hyperdx; overrides DFE_EXTERNAL_ORIGINfollows DFE_EXTERNAL_ORIGIN
DFE_HYPERDX_BASE_URLWhere the ENGINE reaches dfe-hyperdx; name an external one herehttp://dfe-hyperdx:8000
DFE_HYPERDX_TEAMTeam name when the token carries no group claimdfe
DFE_QUERY_READER_CLICKHOUSE_PASSWORDPassword of dfe_query_reader, the platform reader dfe-hyperdx's seeded connection uses, which the engine creates on itgenerated
USAGE_STATS_ENABLEDtrue sends upstream HyperDX's usage stats to in-otel.hyperdx.io, a third party. Upstream's own default is onfalse
NEXT_PUBLIC_THEMEIn env/hyperdx.env. UI theme: dfe, hyperdx or clickstackdfe
HYPERDX_API_KEYIn env/hyperdx.env. Ships dfe-hyperdx's own logs over OTLP when setunset
HYPERDX_LOG_LEVELIn env/hyperdx.env. dfe-hyperdx API log levelinfo
HYPERDX_DOCUMENTDB_USERDocumentDB login dfe-hyperdx useshyperdx
HYPERDX_DOCUMENTDB_PASSWORDPassword for that loginhyperdx
EXPRESS_SESSION_SECRETIn env/hyperdx.env. Signs dfe-hyperdx's session cookiegenerated by make init, unset otherwise, which make post fails
TOKEN_ENCRYPTION_KEYIn env/hyperdx.env. Encrypts the third-party tokens dfe-hyperdx stores, 64 hex charsgenerated by make init, unset (plain text) otherwise, which make post fails
HYPERDX_DOCUMENTDB_VERSIONdocumentdb-local image version, which make stack sets from the DFE stack SSoT once it carries onepinned in docker-compose.yml

hyperdx-documentdb has DOCUMENTDB_USAGE_TELEMETRY off, as upstream reports to a third party hourly. Its 128 MB shared_buffers run inside the service limit, its data directory is pre-initialised so a first start skips initdb and its image's own probe is retuned to gate startup. Compose gives the password a default because a :? would break make down, and make init mints the real one.

dfe-hyperdx mounts only the CA from hyperdx-documentdb-tls, never the gateway's key. DASHBOARD_PROVISIONER_DIR starts the provisioner, which reruns each minute and skips a dashboard whose source the team lacks. HYPERDX_API_PORT and HYPERDX_APP_PORT are the container ports dfe-proxy dials by name. NEXT_PUBLIC_DFE_UI_BASE_URL is where the embedded dfe-hyperdx opens a created rule.

Infra UI authentication (opt-in)​

Arms an oauth2-proxy per infra-UI origin, see operating.md. Off by default and no other profile may require an issuer.

VariableUseDefault
DFE_AUTH_ENABLEDArm the auth compose profile (also a service_profiles.yaml footprint key)false
DFE_OIDC_ISSUER_URLOIDC issuer, required when the profile is armed-
DFE_OIDC_CLIENT_IDOAuth client id, required when the profile is armed-
DFE_OIDC_CLIENT_SECRETOAuth client secret, required when the profile is armed-
DFE_OIDC_ALLOWED_GROUPSGroups allowed through the gate. Blank means authn alone and is refuseddfe-infra,dfe-admin
DFE_OIDC_EMAIL_DOMAINSExtra email-domain restriction on top of the group check*
DFE_OAUTH2_PROXY_COOKIE_SECRETSession cookie key, shared by all three proxies. make init generates 32 bytesgenerated
DFE_OAUTH2_PROXY_COOKIE_DOMAINCookie domain. Blank is a host-only cookie, which shares one session across the boxblank
DFE_OAUTH2_PROXY_COOKIE_SECURESet the Secure cookie flag. true on plain HTTP loops the sign-in and the oauth2-proxies stay plain HTTP under console TLS, so set it only behind TLS of your ownfalse
DFE_OAUTH2_PROXY_EXTERNAL_ORIGINScheme and host only, no port, the OAuth redirect URLs are built from. Overrides DFE_EXTERNAL_ORIGINfollows DFE_EXTERNAL_ORIGIN
DFE_OAUTH2_PROXY_VERSIONOverride the oauth2-proxy image pin. Not SSoT-derived, so keep the tag@sha256 formpinned in docker-compose.yml

App contracts, and the custom environment the console writes​

The console's app settings page reports what an app can be configured with and reads that from the app itself rather than from a list the engine carries. One contract-<app> one-shot per app runs that app's pinned image, writes its config schema and capability catalogue into the shared content volume as root, since a fresh named volume lands root-owned, beside a source.json naming the image it ran and exits. The engine reads them at DFE_ENGINE_APP_CONTRACT_DIR. The one-shot emits into <app>.new and swaps that in whole, because the volume outlives a release. An app that cannot emit leaves its contract absent, last release's included. The engine starts anyway, so a settings page with nothing on it for one app means that app's one-shot logged a failure: docker compose logs contract-<app>.

A setting the app's own schema does not declare is written as an environment variable instead, into a file named for the Compose service: env/dfe-loader.custom.env, beside the env/loader.env that make init creates. Each app reads both, the custom file second, so a custom key beats the same key in the operator's file. The environment: block in docker-compose.yml beats both, which is what keeps a custom key from taking over the broker address or the warehouse credentials. Neither file has to exist.

A per-source instance such as dfe-transform-vrl-<source> reads its app's custom file and then its own, env/dfe-transform-vrl-<source>.custom.env, so a key set on one source reaches that source's container alone. make apply SERVICES=dfe-transform-vrl-<source> recreates it, the command the engine's response names.

The engine alone writes *.custom.env and only for a service it renders. A key you set by hand goes in the operator's file. Earlier checkouts read env/loader.custom.env and its siblings, names the engine never wrote and nothing reads now: move any key you put there into env/loader.env.

A custom env write needs make apply SERVICES=<service>, not a restart. Compose reads env_file when it creates a container, so docker compose restart keeps the environment the container already had. A bare docker compose up -d misses what make resolves from the profile, the broker address included. Add --env-file .env --env-file .tmp/profile.env to hand it the same values, once a make target has resolved the profile.

The engine writes those files into the env/ directory of the checkout it is started from: DFE_ENGINE_DEPLOYMENT_APP_ENV_DIR names it (/app/app-env inside the container, the sibling of DFE_ENGINE_DEPLOYMENT_APP_CONFIG_DIR) and it is a read-write bind mount of ./env, so the deployment's own working tree is what changes when an operator saves a setting.

The engine runs as its image's user, not yours, so the two meet on a group. make init (and every start target) makes env/ group-writable and setgid, the engine container gets the checkout's group through group_add (DFE_DEV_GID, which the Makefile sets to id -g), and each custom file is written 0640 in that group. Compose run by anyone in that group reads it, and nobody else on the host does. A bare docker compose without make adds group 1000. When the engine cannot give a file the directory's group, its log says so and names the fix.

Default Ports​

PortServiceProtocol
3000dfe-proxyWeb UI (envoy fronts dfe-ui, which publishes no host port)
8000dfe-hyperdx-proxydfe-hyperdx API
8003dfe-engineHTTP API
8080dfe-receiverHTTP ingest
8081KafbatWeb UI
8090dfe-hyperdx-proxydfe-hyperdx App UI
8123ClickHouseHTTP API
9000ClickHouseNative protocol
9090dfe-receiverPrometheus metrics
9091dfe-loaderPrometheus metrics
9093dfe-archiverPrometheus metrics
13133otel-collectorhealth_check
19092Kafka (any backend)Plaintext host

The receiver's other listeners publish once their port key is set: 4317 (OTLP gRPC), 4318 (OTLP HTTP), 5044 (Beats), 6000 (Vector gRPC) and 8088 (HEC).

dfe-fetcher and the transforms run one instance per source. An instance publishes no host port.

ClickHouse Schema​

dfe-engine is the schema authority. It ships /app/schemas inside its own image (pinned by DFE_ENGINE_VERSION) and creates the ClickHouse objects at startup; the loader pre-warms those schemas into its cache. Nothing in this repo provisions tables.

  • dfe - master database for all DFE related tables
  • dfe.main - catch-all for unrouted events, carrying _tags (JSON) among the profile columns

That last detail matters more than it looks: the e2e suite and the power-on self test both identify their own rows by reading _tags.marker back out. If a deployment provisions a schema without _tags, both will correctly refuse to claim they verified anything.

Container Images​

Images are published from the component repos:

  • ghcr.io/hyperi-io/dfe-archiver
  • ghcr.io/hyperi-io/dfe-engine
  • ghcr.io/hyperi-io/dfe-fetcher
  • ghcr.io/hyperi-io/dfe-loader
  • ghcr.io/hyperi-io/dfe-receiver
  • ghcr.io/hyperi-io/dfe-transform-elastic
  • ghcr.io/hyperi-io/dfe-transform-vector
  • ghcr.io/hyperi-io/dfe-transform-vrl
  • ghcr.io/hyperi-io/dfe-ui