bella_baxter Ruby SDK
Ruby client for Bella Baxter — load secrets into your Ruby or Rails application with zero runtime dependencies.
Features
- Zero runtime dependencies — uses only Ruby's stdlib (
openssl,net/http,json) - Rails Railtie — auto-loads secrets before
database.ymlis evaluated - End-to-end encryption — optional E2EE using ECDH-P256-HKDF-SHA256-AES256GCM (stdlib OpenSSL)
- Thread-safe — singleton client, mutex-protected HTTP
- ENV injection —
load_into_env!respects existing values (local dev overrides work)
Installation
# Gemfile
gem "bella_baxter"
bundle install
Quick start
require "bella_baxter"
client = BellaBaxter::Client.new(
baxter_url: "https://baxter.example.com",
api_key: "bax-...",
project: "my-app",
environment: "production"
)
secrets = client.all_secrets.secrets
puts secrets["DATABASE_URL"]
Rails integration
Add the gem to your Gemfile. That's all.
The gem ships a Railtie that injects all secrets into ENV during Rails' before_configuration hook — before database.yml is parsed, before any initializers run.
Environment variables to set on your host (the only secrets you need there):
BELLA_BAXTER_URL=https://baxter.example.com
BELLA_API_KEY=bax-...
BELLA_PROJECT=my-app
# BELLA_ENV defaults to Rails.env
# BELLA_E2EE=true to enable end-to-end encryption
database.yml — nothing secret lives here:
production:
adapter: postgresql
url: <%= ENV.fetch("DATABASE_URL") %>
DATABASE_URL is injected by Bella Baxter before Rails reads this file.
Boot order
1. Gemfile loaded → Railtie registered
2. config/application.rb starts
3. [before_configuration] ← Bella Baxter injects ENV here
4. database.yml evaluated ← ENV["DATABASE_URL"] available ✓
5. config/initializers/
6. App boots
ENV injection
# Inject into ENV (existing values are NOT overwritten — local dev wins)
count = BellaBaxter.load_into_env!
puts "Loaded #{count} secrets"
# Force overwrite (e.g. secret rotation without restart)
BellaBaxter.load_into_env!(overwrite: true)
End-to-end encryption
client = BellaBaxter::Client.new(
baxter_url: "https://baxter.example.com",
api_key: "bax-...",
project: "my-app",
environment: "production",
enable_e2ee: true # Client generates P-256 keypair; server encrypts the response
)
# Decryption is transparent — same API
secrets = client.all_secrets.secrets
Algorithm: ECDH-P256 → HKDF-SHA256 → AES-256-GCM. All operations use Ruby's built-in openssl — no extra gems required.
Write operations
# Create
client.create_secret(
provider: "my-vault", # provider slug
key: "DATABASE_URL",
value: "postgres://...",
description: "Primary database"
)
# Update
client.update_secret(
provider: "my-vault",
key: "DATABASE_URL",
value: "postgres://new-host/..."
)
# Delete
client.delete_secret(provider: "my-vault", key: "DATABASE_URL")
Lightweight version check
Avoid transferring all secret values just to check if anything changed:
v = client.secrets_version
puts "Version #{v.version}, last changed #{v.last_modified}"
Global configuration (optional)
# config/initializers/bella_baxter.rb
BellaBaxter.configure do |c|
c.baxter_url = ENV["BELLA_BAXTER_URL"]
c.api_key = ENV["BELLA_API_KEY"]
c.project = "my-app"
c.environment = Rails.env
c.enable_e2ee = Rails.env.production?
end
# Access the singleton client anywhere
BellaBaxter.client.all_secrets
Samples
| Sample | Description |
|---|---|
samples/01-standalone/ |
Pure Ruby script — all usage patterns |
samples/02-rails/ |
Rails integration with database.yml example |
Typed Secret Code Generation
bella secrets generate ruby fetches the secrets manifest (key names + type hints, no values) from the Bella API and generates a typed AppSecrets module. Each method calls ENV.fetch at runtime — no secret values are ever embedded in the generated file.
bella secrets generate ruby \
--project my-app \
--environment production \
--output app_secrets.rb
Generated app_secrets.rb:
# Auto-generated by bella secrets generate ruby — do not edit manually.
module AppSecrets
def self.database_url
ENV.fetch('DATABASE_URL') { raise "Secret 'DATABASE_URL' is not set." }
end
def self.port
ENV.fetch('PORT') { raise "Secret 'PORT' is not set." }.to_i
end
def self.enable_feature_x
v = ENV.fetch('ENABLE_FEATURE_X') { raise "Secret 'ENABLE_FEATURE_X' is not set." }
%w[1 true yes].include?(v.downcase)
end
end
Usage alongside the Ruby SDK
# The Railtie or BellaBaxter.load_into_env! has already populated ENV.
require_relative "app_secrets"
db_url = AppSecrets.database_url # String — raises if missing
port = AppSecrets.port # Integer — parsed automatically
Because each method calls ENV.fetch on every invocation (not memoized), secrets injected or refreshed at any point are always current.
Options
| Option | Default | Description |
|---|---|---|
-p, --project <slug> |
.bella context |
Project slug |
-e, --environment <slug> |
.bella context |
Environment slug |
--provider <slug> |
default |
Provider slug |
-o, --output <path> |
app_secrets.rb |
Output file path |
--class-name <name> |
AppSecrets |
Module name |
--dry-run |
— | Print to stdout without writing |
Requirements
- Ruby >= 2.7
- No runtime gem dependencies