Environment variables
Values live in GitHub Secrets; your statio.yaml only routes which key goes to which
service. The agent writes them to /run/statio/<svc>/<service>.env on tmpfs (RAM) and passes
them to the container — never to persistent disk.
Declare keys, not values
Section titled “Declare keys, not values”# statio.yaml (in your repo): declare the key NAMES per serviceservices: - name: api env: [DATABASE_URL, JWT_SECRET] # values come from GitHub Secrets env_inline: { NODE_ENV: production } # non-secret literals, committed hereThe workflow maps each key to its secret (GitHub masks them in logs):
with: env: | DATABASE_URL=${{ secrets.DATABASE_URL }} JWT_SECRET=${{ secrets.JWT_SECRET }}- Secret values → GitHub Secrets, passed via the Action’s
env:input. - Non-secret config →
env_inlineinstatio.yaml.
Ops-only secrets (server-side base)
Section titled “Ops-only secrets (server-side base)”A server-side base exists for secrets CI should never see:
sudo statio env set api OPS_ONLY --secret-stdin --protected # CI cannot overwrite itsudo statio env set api MUST_HAVE --required # CI must provide it, or 422--protected keys can’t be overridden by a deploy; --required keys must be supplied or the
deploy fails with 422.
At-rest honesty
Section titled “At-rest honesty”The agent runs as root with access to docker.sock, so it’s root-equivalent. CI values are
RAM-only and don’t appear in logs or the response, but there is no magic at-rest
encryption: docker inspect shows them to local root. The real protection is that GitHub
Secrets is the store, the channel is signed, and nothing touches persistent disk. See the
security model for the full reasoning.