Handling Environment Variables in OpenAPI Server URLs
When defining server URLs in OpenAPI specs, you often need to reference environment-specific values (like staging or production subdomains). But using raw environment variables can result in placeholder values like default: unknown
, which may confuse tools like Swagger UI, ReDoc, or code generators.
This guide explains the issue and offers clean solutions β with practical examples.
π§© The Problemβ
When using tools like swagger2openapi
to convert from OpenAPI 2.0 (Swagger) to OpenAPI 3.0, this YAML:
host: ${RUN_APP}-gateway-${PROJECT_RUN_ID}.run.app
gets transformed into:
servers:
- url: https://${{RUN_APP}}-gateway-${{PROJECT_RUN_ID}}.run.app
variables:
RUN_APP:
default: unknown
PROJECT_RUN_ID:
default: unknown
This is valid OpenAPI 3.0, but:
default: unknown is not helpful in rendered docs. You donβt want to expose real values (like UUIDs or production hostnames). Tools like Swagger UI may not render or generate SDKs correctly with unknown.
β Solution 1: Use Safe Default Placeholdersβ
Manually edit or post-process your servers: block to add safe, recognizable defaults:
servers:
- url: https://${{RUN_APP}}-gateway-${{PROJECT_RUN_ID}}.run.app
variables:
RUN_APP:
default: dev
PROJECT_RUN_ID:
default: xyz123
π This:
Keeps sensitive info private. Allows tools like Swagger UI to resolve a real-looking server URL. Keeps your schema valid and compatible with code generators.
β Solution 2: Replace Server Block Dynamicallyβ
Post-process the output YAML in a build script:
import yaml
with open("openapi-v3-autogen.yaml") as f:
data = yaml.safe_load(f)
data["servers"] = [
{ "url": "https://dev-gateway-abc123.run.app" }
]
with open("openapi-for-docs.yaml", "w") as f:
yaml.safe_dump(data, f)
β Use this when:
Generating docs or SDKs. Wanting per-environment baked-in specs (dev, staging, prod).
β Solution 3: Use Environment Substitution in CIβ
If your OpenAPI spec is a template (e.g., openapi.template.yaml):
servers:
- url: https://${{RUN_APP}}-gateway-${{PROJECT_RUN_ID}}.run.app
Then in your CI/CD pipeline:
envsubst < openapi.template.yaml > openapi.yaml
Or with Python:
import os
with open("openapi.template.yaml") as f:
content = f.read()
rendered = content.format(
RUN_APP=os.getenv("RUN_APP", "dev"),
PROJECT_RUN_ID=os.getenv("PROJECT_RUN_ID", "abc123")
)
with open("openapi.yaml", "w") as f:
f.write(rendered)
β Benefits:
Real values injected at runtime. Keeps config DRY. Sensitive data stays outside the repo.
π« Why You Should Avoid default: unknownβ
Breaks Swagger UI rendering Confuses developers reading the spec Causes SDKs to use unknown as the base URL
π Summary Tableβ
Goal Solution Keep variables, safe defaults Add default: dev, default: xyz123 Bake in fixed server Override servers: in script Dynamic server per environment Use CI templating + envsubst
π§ Final Thoughtsβ
default: unknown is technically valid, but practically useless. Consider who will consume your OpenAPI spec: humans, tools, or both. Use the flexibility of OpenAPI 3.0 to keep things DRY and secure.