Skip to content

Environment Variables

Appcircle microservices are configured using environment variables. This convention keeps names consistent across services, gives commonly used suffixes a standard meaning, and keeps names short and readable in container environments.

Naming Format

All Appcircle-owned environment variables must use the following format and start with the AC_ prefix:

AC_[COMPONENT_][MODIFIER_]VARIABLE

COMPONENT is conditional, not automatic: a variable that describes your own service carries no component at all — the file/container it's defined in already establishes which service it belongs to, so restating that would be redundant. A variable that describes a different system — a dependency you call, a service you authenticate to — carries that other system's component, so the reader knows which one is meant.

AC_CACHE_ENABLED
AC_DB_CONNECTION_STRING
AC_KEYCLOAK_EXTERNAL_BASE_URL
AC_S3_ACCESS_KEY

The first two above describe this service's own behavior (its own cache flag, its own database) and carry no component. The last two describe external dependencies (Keycloak, S3) and are named after them.

Operating system, Kubernetes, runtime, and framework variables (PATH, HOME, HOSTNAME, ASPNETCORE_ENVIRONMENT, DOTNET_RUNNING_IN_CONTAINER, KUBERNETES_SERVICE_HOST, ...) are not required to use this prefix.

AC_ENVIRONMENT and AC_IS_SELF_HOSTED carry no component for a stronger reason than "this is my own service": they are deployment-wide — the same value is read identically by every service, not just this one. Use AC_<COMPONENT>_ENVIRONMENT (e.g. AC_API_GATEWAY_ENVIRONMENT) only when one service's environment genuinely differs from the rest; a value that is truly service-specific keeps its component.

PORT is a plain instance of the general rule above — each service's own listening port is its own property, so it carries no component (AC_PORT, not AC_<COMPONENT>_PORT), matching how Heroku and Google Cloud Run both inject a bare PORT per container. When one container exposes more than one port, qualify by purpose, not by service: AC_HTTP_PORT, AC_METRICS_PORT.

Inside a pod, Appcircle variables can be listed using:

printenv | sort | grep '^AC_'

Component Names

COMPONENT follows a DOMAIN_TYPE pattern: DOMAIN is the business area the service belongs to, TYPE is its layer or role within that domain.

STORE_API
STORE_WEB
STORE_ADMIN

TYPE must come from a closed list so the DOMAIN/TYPE boundary stays unambiguous — the same mechanism already used for MODIFIER and the standard keywords.

Type Meaning
API Backend service exposing an API
WEB Public-facing web application
ADMIN Internal administration panel
WORKER Background job or worker process

Cross-cutting infrastructure services that don't belong to a single business domain (API_GATEWAY, KEYCLOAK, KAFKA, EVENT_BUS, ...) are named directly, without forcing a DOMAIN_TYPE split.

The component axis always names the other system a value is about — never the service that owns the file it's written in. A service never prefixes a variable with its own component, full stop: that component is exactly what the surrounding file/container already tells you, so restating it is redundant. This applies to every variable a service owns, not only credentials — a log level, a health-check path, or the service's own external URL are just as much "its own property" as a credential is, and all of them carry no component:

Invalid (inside Resource Server's own config): AC_RESOURCE_API_LOG_MIN_LEVEL, AC_RESOURCE_API_KEYCLOAK_CLIENT_SECRET — both restate the owning service, which the file already establishes. Valid: AC_LOG_MIN_LEVEL (own property), AC_KEYCLOAK_CLIENT_SECRET (names the one thing that actually varies: which dependency this credential is for).

This is directional, not symmetric: when a different service later needs to call Resource Server, that other service's own config uses AC_RESOURCE_API_... — from its point of view, Resource Server is the external dependency being named. A component name is never your own identity; it is always the identity of what you're pointing at.

Modifiers

Modifiers are optional and provide additional context. They are placed directly after COMPONENT, before the value keyword, since they describe the reachability of that specific value rather than the component's identity. Do not use them when only one address exists and there is no ambiguity.

Modifier Meaning
EXTERNAL Address exposed outside the cluster or private network
INTERNAL Address accessible only inside the cluster or private network
AC_EXTERNAL_URL=https://api.appcircle.io
AC_INTERNAL_URL=http://api-gateway.appcircle.svc.cluster.local

Naming Rules

Environment variable names must:

  • Use uppercase letters, separated by underscores.
  • Start with AC_, and include a component name only when naming a different system (see above) — never your own.
  • End with a keyword that describes the value type.
  • Avoid unnecessary abbreviations.

Valid: AC_PORT, AC_CACHE_ENABLED, AC_KEYCLOAK_EXTERNAL_BASE_URL

Invalid: appcircle_api_url, AC-API-GATEWAY-URL, AC_API_GATEWAY_IS_ENABLED, AC_API_GATEWAY_URL_EXTERNAL, AC_EXTERNAL_API_GATEWAY_URL

Standard Keywords

Keyword Meaning Example Notes
PORT Network port the service listens on AC_PORT=8080 No component (see above) — matches the bare PORT Heroku and Google Cloud Run both inject per container. For multiple ports, qualify by purpose, not service: AC_HTTP_PORT, AC_METRICS_PORT
ENVIRONMENT Runtime environment AC_ENVIRONMENT=Production Recommended values: Development, Testing, Staging, Production. Use ENVIRONMENT, not ENVIROMENT. Deployment-wide by default (no component); use AC_<COMPONENT>_ENVIRONMENT only when one service's environment differs from the rest
HOST Hostname or IP, without scheme, port, or path AC_REDIS_HOST=redis.cache.svc.cluster.local Invalid: AC_REDIS_HOST=redis://.... Use ADDRESS when the port is part of the value
ADDRESS host:port, no scheme, no path — the pair a client dials when it resolves the scheme separately (e.g. via a companion _HTTP_ENABLED/_TLS_ENABLED flag) AC_S3_ADDRESS=minio:9000 Use HOST when there is no port; use ENDPOINT when the scheme belongs in the same value. Invalid: AC_S3_ADDRESS=https://minio:9000
ENDPOINT Address of an external dependency/integration a client connects to as a whole — complete scheme://host[:port][/path], matching how OpenTelemetry (OTEL_EXPORTER_OTLP_ENDPOINT), Dapr (DAPR_GRPC_ENDPOINT), and MinIO (MC_STS_ENDPOINT_<alias>) name their exporter/sidecar/storage targets AC_OTEL_COLLECTOR_ENDPOINT=https://otel-collector.appcircle.io:4317 Requires a scheme (unlike HOST/ADDRESS). One ENDPOINT per dependency; for a specific operation/route within that dependency's API use ENDPOINT_URL/ENDPOINT_PATH instead — see below. Invalid: AC_S3_ENDPOINT=minio:9000 (no scheme — use ADDRESS instead), AC_LOGIN_ENDPOINT (ambiguous — the whole dependency, or the /login route?)
URL Complete HTTP or HTTPS address AC_EXTERNAL_URL=https://api.appcircle.io Use BASE_URL for a service's main address, e.g. AC_KEYCLOAK_EXTERNAL_BASE_URL
URLS Multiple URLs, comma-separated AC_ALLOWED_ORIGIN_URLS=https://app.appcircle.io,https://admin.appcircle.io Matches how multi-host values are conventionally listed (e.g. Kafka BOOTSTRAP_SERVERS=host1:port1,host2:port2); avoid JSON arrays
ENDPOINT_URL / ENDPOINT_PATH A specific API operation (a route/action to call once connected) — one dependency has one ENDPOINT but may expose several of these AC_KEYCLOAK_TOKEN_ENDPOINT_PATH=/openid-connect/token Always keep the _URL or _PATH suffix; bare _ENDPOINT is reserved for the whole-dependency meaning above. Login route: AC_KEYCLOAK_LOGIN_ENDPOINT_PATH=/login, not AC_KEYCLOAK_LOGIN_ENDPOINT
PATH Relative URL path or filesystem path AC_LOGOUT_PATH=/api/logout Must not contain a complete URL
URI Generic or non-HTTP resource identifier AC_EVENT_BUS_URI=amqp://rabbitmq:5672 Use URL for HTTP/HTTPS resources, URI otherwise
SCHEME Protocol scheme only AC_SCHEME=https Do not include ://
DB Database-related qualifier AC_DB_NAME=store Use as a qualifier, not standalone (AC_DB=... is invalid)
CONNECTION_STRING Full connection info for a dependency AC_DB_CONNECTION_STRING=mongodb://mongodb:27017/store May contain secrets — provide via Kubernetes Secrets. Or split into _HOST, _PORT, _NAME, _USERNAME, _PASSWORD
VERSION Version of an API, schema, or protocol AC_API_VERSION=v2 The versioned subject must be included in the name
ENABLED Feature or integration switch AC_CACHE_ENABLED=true Values: true / false only
IS_<STATE> State or property AC_IS_READ_ONLY=true Do not combine with ENABLED (AC_IS_ENABLED is invalid)

HOST, ADDRESS, and ENDPOINT form a ladder of increasing detail for naming a network target — pick the one matching exactly what the value contains, never more or less:

  • HOST — hostname/IP alone: AC_REDIS_HOST=redis.cache.svc.cluster.local.
  • ADDRESS — hostname/IP and port, no scheme: AC_S3_ADDRESS=minio:9000. Use this when the port is part of the value but the client resolves the scheme separately (e.g. a companion _HTTP_ENABLED flag).
  • ENDPOINT — full scheme://host[:port][/path] for an entire dependency: AC_OTEL_COLLECTOR_ENDPOINT=https://otel-collector.appcircle.io:4317.

ENDPOINT and ENDPOINT_URL/ENDPOINT_PATH are, separately, different keywords, not variations of one concept, even though both contain the word "endpoint":

  • ENDPOINT names the dependency as a whole — the one address a client connects to for an entire integration (an object-storage server, a telemetry collector, a sidecar).
  • ENDPOINT_URL / ENDPOINT_PATH name one operation within an API — there are typically several per dependency (token, authorization, userinfo, revoke — mirroring how the OIDC spec itself names token_endpoint, authorization_endpoint, userinfo_endpoint), e.g. AC_KEYCLOAK_LOGIN_ENDPOINT_PATH=/login.

Dropping the _URL/_PATH suffix to name a single operation (AC_LOGIN_ENDPOINT) is invalid: it reads as the whole dependency's address and collides with the other meaning.

Complete Example

This example is one hypothetical service's own env — its own properties are bare, its Keycloak dependency is qualified:

AC_ENVIRONMENT=Production
AC_PORT=8080
AC_RATE_LIMIT_ENABLED=true
AC_IS_READ_ONLY=false

AC_EXTERNAL_URL=https://api.appcircle.io
AC_INTERNAL_URL=http://api-gateway.appcircle.svc.cluster.local

AC_KEYCLOAK_EXTERNAL_BASE_URL=https://auth.appcircle.io
AC_KEYCLOAK_INTERNAL_BASE_URL=http://keycloak.identity.svc.cluster.local
AC_KEYCLOAK_TOKEN_ENDPOINT_PATH=/openid-connect/token

AC_DB_CONNECTION_STRING=mongodb://mongodb:27017/store

Kubernetes Example

env:
  - name: AC_ENVIRONMENT
    value: Production

  - name: AC_PORT
    value: "8080"

  - name: AC_EXTERNAL_URL
    value: https://api.appcircle.io

  - name: AC_RATE_LIMIT_ENABLED
    value: "true"

  - name: AC_DB_CONNECTION_STRING
    valueFrom:
      secretKeyRef:
        name: app-database
        key: connection-string

Checklist

Before adding a new environment variable, verify that:

  • It starts with AC_.
  • It uses uppercase letters and underscores.
  • If it describes your own service, it carries no component at all. A component is present only when the variable names a different system — and then it's that system's name, following DOMAIN_TYPE (or a recognized cross-cutting service name), never your own.
  • EXTERNAL or INTERNAL is used only when necessary.
  • Its suffix correctly describes the value.
  • Boolean variables use ENABLED or IS_<STATE>.
  • Sensitive values are loaded from a secret source.