Using the Relay
The relay enables async sync when teammates aren't online at the same time. Messages are stored encrypted and delivered when peers come online.
When You Need a Relay
envctl works peer-to-peer by default. The relay is optional but useful when:
- Team members are in different time zones — Peers rarely online simultaneously
- Working remotely — Direct P2P connections blocked by firewalls/NAT
- Guaranteed delivery — Changes must reach everyone, even if they're offline
- Onboarding new members — They need to sync before meeting other peers
You don't need the relay if:
- Team is on the same local network (mDNS discovery works)
- Team is small and online at similar times
- You prefer fully P2P operation
- You can run an always-on node (see below)
Alternative: Always-On Node
If your team shares a network (physical LAN or VPN), you can run an always-on node instead of using a relay. This is a machine that runs the envctl daemon continuously, acting as a persistent peer that's always available for sync.
When to Use an Always-On Node
- Team shares a physical network or VPN
- You have an always-on machine (server, NAS, Raspberry Pi, cloud VM)
- You want to avoid external infrastructure entirely
- You prefer a free, self-hosted solution
Always-On Node vs Relay
| Feature | Always-On Node | Relay Server |
|---|---|---|
| Cost | Free (your hardware) | Free tier / Paid plans |
| Network requirement | Shared network or VPN | Internet access only |
| Setup complexity | Run daemon on a server | Single command |
| Works across internet | Only via VPN | Yes |
| External dependency | None | Relay service |
| Metadata visibility | You control everything | Relay sees metadata |
| Maintenance | You maintain the node | Managed for you |
Setting Up an Always-On Node
Any machine that can run envctl can be an always-on node. Common choices:
- Office server or NAS
- Raspberry Pi on the network
- Cloud VM (if using a VPN)
- Any always-on workstation
1. Install envctl on the node
$ curl -fsSL https://raw.githubusercontent.com/uradical/envctl/main/install.sh | sh
2. Create an identity for the node
$ envctl init --name sync-server --keychain
✓ Identity created
Name: sync-server
Fingerprint: sha256:9a8b7c6d...
Using --keychain stores the passphrase so the daemon can start without manual intervention.
3. Join the project
Invite the node to your project like any team member:
# On your machine:
$ envctl project invite sync-server --pubkey 9a8b7c6d... --env dev,staging,prod
# On the always-on node:
$ envctl join eyJwcm9qZWN0Ijoi...
4. Install as a system service
Configure the daemon to start automatically on boot. The recommended approach depends on your platform.
Linux (systemd)
Create a systemd service file:
$ sudo nano /etc/systemd/system/envctl.service
Add the following configuration:
[Unit]
Description=envctl P2P secrets sync daemon
After=network-online.target
Wants=network-online.target
[Service]
Type=simple
User=envctl
Group=envctl
ExecStart=/usr/local/bin/envctl daemon run
Restart=on-failure
RestartSec=5
Environment=HOME=/home/envctl
[Install]
WantedBy=multi-user.target
If running as a dedicated user, create one first:
$ sudo useradd -r -m -s /bin/false envctl
Then set up the identity and project for that user before enabling the service. Alternatively, change User= and Group= to your own user account.
Enable and start the service:
$ sudo systemctl daemon-reload
$ sudo systemctl enable envctl
$ sudo systemctl start envctl
Check the service status:
$ sudo systemctl status envctl
● envctl.service - envctl P2P secrets sync daemon
Active: active (running) since Mon 2024-03-01 10:00:00 UTC
Main PID: 12345 (envctl)
Memory: 24.0M
CPU: 1.234s
CGroup: /system.slice/envctl.service
└─12345 /usr/local/bin/envctl daemon run
View logs with journalctl:
$ sudo journalctl -u envctl -f
macOS (launchd)
The built-in installer creates a launchd plist:
$ envctl daemon install
✓ Installed as system service
$ launchctl load ~/Library/LaunchAgents/io.envctl.daemon.plist
To start on boot, the plist is automatically loaded on login. Check status with:
$ launchctl list | grep envctl
5. Verify it's running
$ envctl daemon status
✓ Daemon running (PID 12345)
P2P port: 7834
Uptime: 3 days, 2 hours
How It Works
Once the always-on node is running:
- When you push changes, the node receives them (it's always online)
- When teammates come online, they discover the node via mDNS or saved peers
- They sync with the node, receiving any changes they missed
The node acts as a hub that's always available, solving the "nobody online" problem without external infrastructure.
VPN Considerations
If your team uses a VPN to create a shared network:
- The always-on node should be connected to the VPN
- mDNS may not work across some VPNs—add peers manually:
$ envctl peers add 10.8.0.1:7834 - Consider running the node on your VPN server itself
Best of both worlds
You can use both an always-on node and a relay. The node handles sync within your network, while the relay provides backup for team members who can't connect to the VPN.
Setting Up the Relay
Configure a relay for your project:
$ envctl project relay set relay.envctl.dev
✓ Relay configured for myapp
URL: wss://relay.envctl.dev/ws
This change is recorded in your project's membership chain and automatically synced to all team members.
Using the Public Relay
The public relay at relay.envctl.dev is available for all envctl users:
| Tier | Retention | Rate Limits |
|---|---|---|
| Free | 7 days | Yes |
| Pro | 30 days | No |
| Enterprise | Unlimited | No |
Self-Hosting
For Enterprise users or those who prefer self-hosting, you can run your own relay:
$ envctl project relay set relay.yourcompany.com
✓ Relay configured for myapp
URL: wss://relay.yourcompany.com/ws
Contact sales@envctl.dev for self-hosting documentation.
How the Relay Works
Message Flow
- You make a change (e.g.,
envctl env var set API_KEY=...) - The daemon encrypts the operation for each team member
- Online peers receive it directly via P2P
- For offline peers, the message is sent to the relay
- The relay stores the encrypted message
- When peers come online, they retrieve their messages
What the Relay Sees
| Data | Visible to Relay? |
|---|---|
| Secret values | No (encrypted) |
| Variable names | No (encrypted) |
| Project name | Yes (for routing) |
| Sender/recipient IDs | Yes (for routing) |
| Message timestamps | Yes |
| Message sizes | Yes |
| IP addresses | Yes (connection logs) |
End-to-end encryption
The relay is a store-and-forward service. It can see metadata but never the contents of your secrets. Messages are encrypted with ML-KEM before leaving your machine.
Checking Relay Status
$ envctl project relay status
Relay Status for myapp:
URL: wss://relay.envctl.dev/ws
Status: connected
Last message: 2 minutes ago
Pending messages: 0
The daemon maintains a persistent WebSocket connection to the relay.
Removing the Relay
To disable relay sync and return to P2P-only:
$ envctl project relay set ""
✓ Relay disabled for myapp
Troubleshooting
Connection Issues
If the relay shows as disconnected:
- Check network connectivity:
$ curl -I https://relay.envctl.dev HTTP/2 200 - Check daemon status:
$ envctl daemon status ✓ Daemon running (PID 12345) - Restart the daemon:
$ envctl daemon stop $ envctl daemon start
Messages Not Syncing
If changes aren't reaching teammates:
- Verify relay is configured for all team members:
$ envctl project relay status - Check that both parties have the daemon running
- Verify the recipient's fingerprint matches their actual identity
Rate Limiting (Free Tier)
The free tier has rate limits. If you see "rate limited" errors:
- Wait a few minutes and try again
- Upgrade to Pro for unlimited usage
- Batch your changes (fewer, larger updates)
Privacy Considerations
While your secrets are encrypted, the relay does see some metadata:
- Traffic patterns — When you sync, how often, message sizes
- Team structure — Who communicates with whom
- IP addresses — Where you're connecting from
If this metadata is sensitive for your use case:
- Self-host a relay on your own infrastructure
- Use a VPN to mask IP addresses
- Stick to P2P-only (no relay)