Option 1: Deploy token (recommended)
The same model as every managed platform. The droplet holds two environment variables and nothing else. On your laptop, in the project directory:--init so the container gets a real PID 1. capy run forwards SIGINT, SIGTERM, and SIGHUP to your process, but it is not a process reaper — pair it with Docker’s init if your app spawns children.
Neither value in that file is a secret on its own. SECRETS_BLOB is inert without a live call to the Capy service, the service refuses revoked tokens, and no plaintext ever touches the droplet’s disk.
To rotate: re-run capy deploy, replace the file, restart the container. To cut access: run capy deploy list to find the token’s id prefix, then capy deploy revoke <deployId>.
Option 2: Droplet-resident identity
Sometimes you want the droplet to be a member of the org — every decryption attributed to a person, secrets updated withcapy the same way as on a laptop. This trades availability for auditability. Read the tradeoff before choosing it.
Logging in without a browser
capy signs in through your browser with a callback on localhost. A headless droplet has no browser, so forward the callback ports over SSH and use the browser you already have. The CLI takes the first free port in the range 19420–19424, so forward all five:
capy stops with “no encryption key on this device” until you move one across yourself. Mint a transport code on your laptop first:
capy redeem authenticates you and then installs your wrapped master key. During that sign-in the CLI prints an authentication URL and tries to open a browser, which fails on a headless box — that is expected. Paste the URL into your laptop’s browser instead. The redirect lands on your laptop’s localhost, the tunnel carries it to the droplet, and the CLI completes the flow. You have five minutes before it times out. See switching computers for the rest of the transport flow.
This is a one-time step. Afterwards the droplet holds a session and refreshes it silently.
That last capy asks which project the directory belongs to — pick yours rather than the New project default. Capy pulls the project’s development branch and writes keep.lock plus an encrypted .env into /srv/myapp; run capy checkout <branch> if the droplet should track a different branch. Both files are safe at rest — every value in .env is a capy:… snippet, never the full plaintext.
Wrapping the container
capy run cannot inject into a container that is already running, so it has to be what launches the container:
capy run’s environment:
capy to pull the latest secrets, then capy run -- docker compose up -d.
Which one
The deciding question is what happens when the person leaves. With a droplet identity, their session ends and the next
capy run on the box fails — you cannot deploy or restart the app through Capy until someone else’s identity is on the droplet. Production is coupled to an individual’s account. Deploy tokens have no such coupling, which is why they are the default recommendation for anything load-bearing.
Both modes call the Capy service once, when capy run starts: inside the container on every container start with a deploy token, on the host at each deploy with a droplet identity. If that call fails, nothing launches — in exchange, revocation takes effect on the very next capy run rather than whenever a cached key expires. Once your process is running, a service outage is invisible to it.
See also
Docker
Entrypoint patterns, Compose, and Kubernetes.
capy run
What each mode needs at startup.