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_ENABLEDflag).ENDPOINT— fullscheme://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":
ENDPOINTnames 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_PATHname one operation within an API — there are typically several per dependency (token, authorization, userinfo, revoke — mirroring how the OIDC spec itself namestoken_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. EXTERNALorINTERNALis used only when necessary.- Its suffix correctly describes the value.
- Boolean variables use
ENABLEDorIS_<STATE>. - Sensitive values are loaded from a secret source.