Skip to main content

First Run

Now that EnvCat is installed, let's create your first bundle and experience the device-approval flow.

Overview

In this guide, you'll:

  1. Seed an example bundle with sample environment variables
  2. Build the CLI binary
  3. Request secrets using device-approval flow
  4. Inject secrets into your shell

Time to complete: ~10 minutes

Step 1: Seed Example Bundle

EnvCat includes a seed script that creates a bundle named dev/example with edge-case test values.

docker compose exec api python seed.py

Output:

Created bundle: dev/example
Added 8 keys to bundle
✓ Seed complete

What Got Created?

The dev/example bundle contains:

KeyValuePurpose
HELLOworldSimple string
SPACEYhello worldValue with spaces
DOLLAR_VALcost is $5Dollar sign (not variable)
QUOTE_MIXHe said "hi" then 'bye'Mixed quotes
HASHYvalue with # hash charHash character (not comment)
MULTILINEline1\nline2Multi-line value
URLpostgres://user:pa$$@localhost:5432/dbDatabase URL with special chars
UNICODEκόσμεUnicode characters

Why these values? They test the CLI's shell-safe quoting implementation.

Verify Bundle Created

Via API:

curl http://localhost:8888/api/v1/bundles | jq

Expected:

[
{
"id": "...",
"name": "dev/example",
"description": "Example bundle with edge-case values",
"tags": "dev,example",
"created_at": "...",
"updated_at": "..."
}
]

Via Web UI:

open http://localhost:8888/bundles

You should see the dev/example bundle listed.

Step 2: Build the CLI

The CLI is written in Go and needs to be compiled.

cd cli
go build -o envcat ./cmd/constants

Output:

go: downloading github.com/mdp/qrterminal/v3 ...
go: downloading github.com/skip2/go-qrcode ...

First build: Takes 30-60 seconds (downloads dependencies).

Verify CLI built:

./constants --help

Expected:

constants - Local-first environment variable management

Usage:
constants request [flags]

Flags:
--api-base string API base URL (default "http://localhost:8888")
--bundle string Bundle ID or name
--keys strings Specific keys to request
--format string Output format: sh, fish, .env (default "sh")
--write string Write to file instead of stdout

Step 3: Request Secrets (Device-Approval Flow)

Now for the fun part! Experience the device-approval flow.

Terminal: Initiate Request

./constants request --bundle dev/example

What happens:

  1. CLI generates ephemeral keypair (Ed25519)
  2. CLI sends request to API with public key
  3. CLI displays QR code and approval URL
  4. CLI polls /api/v1/requests/{id}/wait (every 3 seconds)

You'll see:

🔐 Environment Variable Request
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━

📱 Scan this QR code or open the URL below:

[ASCII QR CODE]

🔗 http://localhost:8888/approve/abc123xyz

⏳ Waiting for approval...

Keep this terminal open! The CLI is polling for approval.

Browser: Approve Request

  1. Open the URL in your browser (or scan QR with phone)
  2. You'll see the approval page at http://localhost:8888/approve/abc123xyz

Approval UI shows:

  • Request ID
  • Suggested bundle: dev/example
  • All available bundles (dropdown)
  • Keys to send (checkboxes)

Steps:

  1. ✓ Bundle dev/example is pre-selected
  2. ✓ All keys are pre-checked
  3. Click "✓ Approve 8 key(s)"

What Happens on Approval?

Behind the scenes:

  1. Web UI calls /api/v1/requests/{id}/encrypt-from-bundle

    • Sends {bundle_id: "...", include_keys: ["HELLO", "SPACEY", ...]}
  2. API (Flask):

    • Loads bundle items from SQLite
    • Decrypts values at-rest (using BUNDLE_KMS_KEY)
    • Encrypts to CLI's public key (libsodium sealed box)
    • Stores ciphertext in Redis with status: "ready"
  3. CLI receives response:

    • Polling returns {status: "ready", ciphertext_base64: "..."}
    • CLI decrypts using private key
    • CLI parses JSON: {"HELLO": "world", ...}
    • CLI outputs shell-safe exports

Terminal: Receive Secrets

Back in your terminal, you'll see:

✓ Approved! Received 8 variable(s)

export HELLO=world
export SPACEY='hello world'
export DOLLAR_VAL='cost is $5'
export QUOTE_MIX='He said "hi" then '"'"'bye'"'"''
export HASHY='value with # hash char'
export MULTILINE='line1
line2'
export URL='postgres://user:pa$$@localhost:5432/db'
export UNICODE=κόσμε

Notice: The CLI correctly quoted all edge cases!

Step 4: Use the Secrets

Now that you have the exports, let's use them.

Option A: Inject into Current Shell

eval "$(./constants request --bundle dev/example)"

Then verify:

echo $HELLO
# world

echo $SPACEY
# hello world

echo $DOLLAR_VAL
# cost is $5

echo $URL
# postgres://user:pa$$@localhost:5432/db

Why eval? A child process (CLI) cannot modify its parent shell's environment. You must use eval to execute the export statements in the current shell.

Option B: Write to .env File

./constants request --bundle dev/example --write .env

Check file:

cat .env

Expected (.env format):

HELLO=world
SPACEY="hello world"
DOLLAR_VAL="cost is $5"
QUOTE_MIX="He said \"hi\" then 'bye'"
HASHY="value with # hash char"
MULTILINE="line1\nline2"
URL="postgres://user:pa$$@localhost:5432/db"
UNICODE="κόσμε"

File permissions: Automatically set to 0600 (read/write owner only) for security.

Option C: Fish Shell

If you use Fish shell:

./constants request --bundle dev/example --format fish | source

Output format:

set -x HELLO world
set -x SPACEY "hello world"
...

Step 5: Create Your Own Bundle

Now let's create your own bundle for a real project.

Via Web UI

  1. Open http://localhost:8888/bundles
  2. Click "+ New Bundle"
  3. Fill in:
    • Name: dev/myproject
    • Description: Development environment for my project
    • Tags: dev,myproject
  4. Click "Create"
  5. Click "Manage" on your new bundle
  6. Click "+ Add Key":
    • Key: API_KEY
    • Value: sk_test_12345
    • Is Secret: ✓ (checked)
  7. Repeat for other keys
  8. Or click "Import" to paste a .env file

Via API

# Create bundle
curl -X POST http://localhost:8888/api/v1/bundles \
-H "Content-Type: application/json" \
-d '{
"name": "dev/myproject",
"description": "Development secrets",
"tags": "dev"
}' | jq

# Save bundle ID from response
BUNDLE_ID="..."

# Add keys
curl -X POST "http://localhost:8888/api/v1/bundles/$BUNDLE_ID/items" \
-H "Content-Type: application/json" \
-d '{
"key": "API_KEY",
"value": "sk_test_12345",
"is_secret": true
}'

Import from .env File

Have an existing .env file? Import it:

# Get bundle ID
BUNDLE_ID=$(curl -s http://localhost:8888/api/v1/bundles | jq -r '.[] | select(.name=="dev/myproject") | .id')

# Import .env
curl -X POST "http://localhost:8888/api/v1/bundles/$BUNDLE_ID/import" \
-H "Content-Type: application/json" \
-d "{\"format\":\"env\",\"payload\":\"$(cat .env)\"}"

Common First-Run Issues

CLI Not Found

Error: bash: ./constants: No such file or directory

Solution: Make sure you're in the cli/ directory and built the binary:

cd cli
go build -o envcat ./cmd/constants
./constants --help

Request Expired

Error: CLI shows Error: request expired or not found (404)

Cause: You took longer than 5 minutes to approve.

Solution: Requests have a 5-minute TTL. Just create a new request:

./constants request --bundle dev/example

Bundle Not Found

Error: Bundle 'dev/example' not found

Solution: Seed the example bundle:

docker compose exec api python seed.py

QR Code Garbled

Issue: QR code looks weird or unreadable.

Solution: The ASCII QR requires color terminal support. Just use the URL shown below the QR code.

Variables Not in Shell

Issue: Ran ./constants request but echo $HELLO shows nothing.

Solution: You must use eval:

# Wrong (child process can't modify parent)
./constants request

# Right (eval executes in current shell)
eval "$(./constants request)"

Next Steps

Congratulations! You've successfully:

  • ✓ Installed EnvCat
  • ✓ Seeded example bundle
  • ✓ Built CLI binary
  • ✓ Experienced device-approval flow
  • ✓ Injected secrets into your shell
  • ✓ Created your own bundle

Explore More

Use in Your Workflow

# Add to your shell profile (.bashrc, .zshrc, etc.)
alias secrets='eval "$(path/to/constants request)"'

# Then in new terminals:
secrets --bundle dev/myproject

Team Usage

  • Share bundles: Use Web UI to manage team bundles
  • Per-environment bundles: dev/myproject, staging/myproject, prod/myproject
  • Per-client bundles: client-acme, client-globex (for consultants)

Need help?Troubleshooting Guide