From the team behind aidrop.it — one workspace to build, host, and keep changing your code.
A project has a web app, a worker, and a scheduled export job. They share a database. Only the web app takes payments, yet all three containers receive the payment provider's secret because somebody attached the project's whole environment to each deploy.
Store a value once at project scope, then select which services receive its name. Shared storage solves rotation. A per-service allowlist solves reach. You need both, because one canonical copy sent everywhere still gives every process the power of that credential.
Project scope removes duplicate copies
A value such as DATABASE_URL belongs to the product, not to one container. Keeping it at project scope means the web app and worker read the same canonical value, and rotating it does not require finding two dashboards and a forgotten .env file.
Project scope should describe ownership, not distribution. The project may hold STRIPE_SECRET_KEY, SMTP_PASSWORD, and S3_BACKUP_KEY while each service receives only the names it uses. Treat the service's list as part of its runtime contract, beside the port and health path.
That separation strengthens the rule that a secret should arrive at run time rather than enter the image. The platform stores one value, the deploy selects it by name, and the process receives it in its environment. Source code, build layers, and unrelated services receive nothing.
An allowlist turns missing access into a useful failure
With an allowlist, adding process.env.STRIPE_SECRET_KEY to the worker does not grant access. The next deploy either refuses because the worker declared a required name the project has not stored, or the process starts without a name it never requested. Both outcomes expose the mismatch.
Without an allowlist, the code works and the permission stays invisible. A compromised image, debug endpoint, or dependency in the export job can now read a payment credential that job never needed. Broad environment injection converts one service failure into a project-wide credential incident.
The list also makes review possible. A diff that adds SMTP_PASSWORD to the worker's required names asks a clear question: what new behaviour needs it? A dashboard switch labelled "all project secrets" asks nothing and leaves no smallest set to compare.
Names can be visible when values cannot
An agent needs to know that DATABASE_URL exists and that the API requires it. It does not need the connection string. Returning value names lets the agent compare the project's inventory with each service's allowlist, spot a missing requirement, and prepare a deploy without placing a credential in the conversation.
No read path should answer the value back. A tool transcript is a log, so returning a secret to help an agent configure a service creates the copy the design was meant to avoid. A person or write-only tool can set it; the agent works from the name and the date it was last changed.
Rotation still needs a dependency map
One stored value can feed several services. Rotation changes the canonical copy, but each consumer may need a restart or deploy to receive it. The allowlists provide the dependency map: search for the name, update the value once, then restart only the services that declare it.
Separate credentials when two services do not need the same authority. SMTP_PASSWORD shared by the web app and export job still ties their rotation together. Provider support permitting, issue one credential per purpose, such as WEB_SMTP_PASSWORD and EXPORT_BUCKET_KEY, so revoking one does not interrupt the other.
Keep a value access matrix
Commit the names and consumers, never the values. This matrix works with any host that supports per-service environment selection. Compare it with the live service settings before each release and during rotation.
# Project value access matrix
VALUE NAME OWNER SERVICES ALLOWED ROTATION / RESTART
DATABASE_URL platform web, worker both
STRIPE_SECRET_KEY payments web web
SMTP_PASSWORD product worker worker
S3_BACKUP_KEY operations backup backup
RULES
[ ] No wildcard or "all project values" attachment
[ ] Every service declares the names it reads
[ ] A missing declared value blocks the deploy
[ ] Reads return names and timestamps, never values
[ ] Rotation lists every consumer before the old value is revoked
{
"service": "worker",
"required_secret_names": ["DATABASE_URL", "SMTP_PASSWORD"]
}
FAQ
Should secrets belong to a project or a service? Store the canonical value at project scope when several services may use it, then give each service an explicit allowlist of names. This avoids duplicate copies without sending every value to every process.
Why are secret names safe to show an agent? A name such as DATABASE_URL describes an interface and carries no credential. It lets the agent configure code and compare requirements. The value must remain write-only because returning it would place it in the model context and tool logs.
How does aidrop.it limit values by service? On aidrop.it, values live on the Project. Each service_build records required_secret_names, and only the intersection of that list with stored Project values reaches the container. A build that requires an unset name is refused before build work starts, while service_get returns names and timestamps without values.
Do I need separate credentials for every service? Use separate credentials when services need different authority or rotation schedules. Sharing one database connection may be appropriate; sharing a payment or storage key with a service that never calls that provider is not.
Get it running from the session you are already in
Connect your coding agent over MCP and ask it to deploy. aidrop builds the repository and runs it on a public address; when a build or the app fails, the log says why, and your agent fixes it and builds again.