Skip to main content

Handling Environment Variables in OpenAPI Server URLs

Β· 3 min read
Serhii Hrekov
software engineer, creator, artist, programmer, projects founder

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.