The docs are wrong. Storing secrets in a `.env` file in your project directory is a bad default for anything beyond a toy lab. It's a single misconfiguration away from being committed to git, leaked in a container build, or scooped up by a web app framework that accidentally serves static files.
The core problems:
* **It conflates code and config.** Your `superagi` directory now contains both.
* **No key management.** No rotation, no audit trail, no access controls. It's a plaintext file.
* **Default installs often have the web UI exposed.** If you're self-hosting SuperAGI, you're likely exposing a port. That increases the attack surface; a path traversal or RCE bug could hand over that `.env` file.
You need to separate secrets from your deployment. Here's what I do:
**For a homelab deployment (Docker):**
Use Docker secrets or bind-mount a secrets volume from a *secure* location (e.g., a `secrets/` directory outside the project, with strict permissions). Your `docker-compose.yml` should reference the environment variables from the container's environment, which you can set via a platform secret store or a `.env` file *on the host only* that is never part of the container build context.
```yaml
# docker-compose.yml snippet
services:
superagi:
environment:
- OPENAI_API_KEY=${OPENAI_API_KEY}
- DB_PASSWORD=${DB_PASSWORD}
# Do NOT use env_file: .env
```
Then, you populate `OPENAI_API_KEY` on your Docker host from a proper vault, or from a host-level `.env` in a secure directory.
**For a more serious deployment:**
Use a secret manager. HashiCorp Vault, AWS Secrets Manager, even Kubernetes Secrets (base64 is not encryption, but it's a step better). SuperAGI doesn't have native integration for these, so you inject the secrets as environment variables at runtime.
The immediate fix if you must use a file: place it outside your web root, with strict permissions (chmod 600), and load it explicitly only in your orchestration layer. Never let the application itself automatically discover it from a default location like the current working directory.
What's everyone else doing? I'm betting most default installs are wide open.
-- mike
-- mike
Yeah, you're spot on about the web UI exposure. I've seen so many people just docker run -p 3000:3000 and forget that the entire project root is the context. If the framework's static file serving is misconfigured even a little, that `.env` is just sitting there.
For the Docker Compose route you mentioned, I always add a `read_only: true` to the service mounts and explicitly bind-mount just the config files needed. It stops any accidental writes, and if a bug tries to read outside its allowed bind-mount, it hits a brick wall.
A neat trick for homelab is using a separate, small `docker-compose.secrets.yml` that you keep outside the main repo and source with `docker-compose -f ... -f ... up`. The main compose file defines the structure, the secret one injects the credentials. Makes it easier to share the setup without sharing the keys.
Fearless concurrency, fearless security.
The separate secrets compose file is a solid workaround, but it's still a bandage on the bigger issue. It assumes you have the discipline to never merge that second file back into your repo by accident. How many people actually set up a pre-commit hook to block that?
The real failure is vendors like SuperAGI baking this into their official docs as the default. It trains new users into a dangerous pattern from day one. They should be pointing directly to Docker secrets or a vault integration, even if it's just a link in a "production setup" section.
hm
Agreed on all points, especially conflating code and config. That's the root cause of most leaks here.
Even your Docker homelab advice has a common tripwire. People will still mount that host `.env` file into the container because the app's entrypoint script expects it in the root directory. The official images often bake that in. So you're fighting the framework's design.
The real fix is patching the app to read from a different location, or better, from a standard like `/run/secrets`. Most won't do that. So the advice becomes "don't use the default image, build your own." Which is why the docs are failing.
Keep it technical.
The host-only .env is still a plaintext file. If you're worried about web UI path traversal, an attacker on the host can read that file too if the permissions are wrong.
Bind-mounting from /etc/secrets or using docker secret creates a proper isolation boundary. The container sees it as a file in a tmpfs or a dedicated mount, not a host path. That blocks host-level file reads unless the attacker breaks the kernel namespace.
The real issue is the app design. It forces you to choose between a container escape and a host file leak. Neither is good.
Capabilities are a start.
Okay, so when you say "bind-mounting from /etc/secrets creates a proper isolation boundary," is that something you configure in the docker-compose file itself? Like, you'd point the container's environment variable file path to that mount instead of a host .env?
I'm still wrapping my head around how the app even knows to look there instead of its own root directory. Does that mean you have to modify the SuperAGI image to change where it reads the config from? That feels like a big step from what the docs say.
Right, and that's the practical snag. You *can* configure a bind mount in compose to put a file at, say, `/run/secrets/env` inside the container. But you're correct, the app still needs to be told to read from that path instead of `./.env`. The default SuperAGI image likely has a hardcoded expectation.
So your choice is to either:
* Modify the entrypoint script or command in your compose file to set the config path variable (if the framework supports it).
* Build a custom image that changes the default source.
It is a step, and it's exactly why the official docs' default is so problematic. It locks you into a pattern that's hard to change later without rebuilding your deployment.
SLSA >= 2 or go home