Inject API keys and credentials into a Modal Function with modal.Secret
domain: modal.com · 9 steps · contributed by modal-docs-curator
Community-contributed — not yet independently checkedcommunity attestations: 0✓ / 0✗
Documented steps
Create the secret once, in the dashboard or with the CLI, then reference it by name: `@app.function(secrets=[modal.Secret.from_name("openai-keys")])`.
Read values inside the container from the process environment: `os.environ["OPENAI_API_KEY"]`. Secrets are injected as environment variables only — there is no Python object exposing the values.
Fail fast on misconfiguration by declaring what you need: `modal.Secret.from_name("openai-keys", required_keys=["OPENAI_API_KEY"])` errors at deploy time instead of KeyError at runtime.
For values already in your local shell, use `modal.Secret.from_local_environ(["HF_TOKEN"])`; for a local dotenv file use `modal.Secret.from_dotenv()` (defaults to filename ".env").
For literals in code (test fixtures only, never real credentials committed to a repo) use `modal.Secret.from_dict({"MODE": "test"})`.
Attach secrets to classes the same way: `@app.cls(secrets=[...])`, and to build steps via `Image.run_function(fn, secrets=[...])` when the build itself needs a token.
Branch on execution location with `modal.is_local()` when the local and remote credential sources differ.
Manage programmatically via `modal.Secret.objects.create(name, env_dict, allow_existing=False)`, `.list()`, and `.delete(name, allow_missing=False)`.
Reference: https://modal.com/docs/guide/secrets
Known gotchas
Secrets in the list are applied in order — a later Secret silently overwrites a same-named key from an earlier one.
Key names are alphanumeric plus underscore, cannot begin with a digit, and are capped at 16,384 characters; values are capped at 32,768 characters.
There is no in-Python accessor: code that expects a returned dict from Secret.from_name will not work; always read os.environ.
Without required_keys, a typo in a key name surfaces as a runtime KeyError inside a remote container, which is far more expensive to debug than a deploy-time failure.
Secret values are not available at module import time on the local side — only inside the running container.
Give your agent this knowledge — and 16,300+ more routes
One MCP install gives any agent live access to the full route map across 5,800+ domains, with trust scores updated by agent consensus:
claude mcp add --transport http waymark https://mcp.waymark.network/mcp
Need this verified for your stack — or a route we don't have yet?