Git for your secrets.

Peer-to-peer sync, post-quantum encryption, signed audit trail.
No cloud required.

$ envctl init
Generating identity...
 Fingerprint: sha256:7f3a...

$ envctl env var set API_KEY=sk_live_...
 Secret encrypted and signed

$ envctl status
 Synced with 2 peers

Why envctl?

Zero Trust

Secrets never leave your hardware unencrypted. No cloud storage, no SaaS dependencies.

P2P Sync

Sync secrets across the internet with remote teammates. Optional relay for async sync when peers are offline.

Post-Quantum

ML-KEM-768 encryption protects your secrets against future quantum attacks.

Audit Trail

Every change is cryptographically signed. Know who changed what, and when.

Git-like Workflow

Familiar commands: push, pull, status, log. Branch-like environments for dev, staging, prod.

Remote Teams

Works across the globe. Invite distributed teammates with a single command. Revoke access instantly.

Install

From Source

go install envctl.dev/go/envctl@latest

See releases for binaries and other platforms.

Quick Start

1

Initialize your identity

$ envctl init

Generates your keypair. This is your identity across all projects.

2

Create a project

$ envctl project create

Sets up envctl in your current directory.

3

Add secrets

$ envctl env var set API_KEY=sk_live_...

Secrets are encrypted immediately with your key.

4

Invite your team

$ envctl team invite alice@example.com
Share this with your teammate:
  envctl join abc123...

Teammates join with a single command.

5

Use your secrets

$ envctl env use prod
 .env written (2 secrets)

Materializes decrypted .env file for your current session.

Commands

envctl init Generate identity
envctl project create Create project
envctl join Join existing project
envctl env var set KEY=val Add or update secret
envctl env var delete KEY Remove secret
envctl env var list Show secrets
envctl env use <env> Write .env file
envctl env create <name> Create environment
envctl env delete <name> Delete environment
envctl status Show sync status
envctl log View history
envctl project invite Invite teammate
envctl project relay set <url> Enable relay sync
envctl project relay status Check relay connection
envctl daemon start Start background daemon
envctl daemon stop Stop daemon

Relay Server

The relay enables async sync when teammates are offline. Messages are stored encrypted and delivered when peers come online. The relay never sees your secrets—only encrypted blobs.

1

Enable relay for your project

$ envctl project relay set wss://relay.example.com/ws

Configure a relay URL. This is recorded in your project chain and shared with teammates.

2

Check relay status

$ envctl project relay status
Relay Status for myproject

  URL: wss://relay.example.com/ws
  Status: connected

The daemon maintains a persistent connection and automatically reconnects if disconnected.

3

Sync happens automatically

When you push secrets, they're sent to online peers directly via P2P. For offline peers, messages are encrypted and stored on the relay until they reconnect.

Self-host your relay

Run your own relay server for full control. The relay is a simple Go binary that stores encrypted messages temporarily.

$ docker run -p 8080:8080 ghcr.io/uradical/envctl-relay

Environments

Projects have multiple environments (dev, staging, prod) with separate secrets. Team members can have access to specific environments.

1

List environments

$ envctl env list
Environments for 'myproject':

* dev          (3 members) [default]
  staging      (2 members)
  prod         (1 member)

See all environments and who has access. The asterisk marks your current environment.

2

Create a new environment

$ envctl env create qa
 Created environment 'qa'

Add environments as your project grows. New members get access to default environments only.

3

Switch environments

$ envctl env use staging
 .env written (5 secrets)

Decrypts and writes secrets for the selected environment to your .env file.

4

Delete an environment

$ envctl env delete old-env --force
 Deleted environment 'old-env'

Remove environments you no longer need. Use --force if members still have access.