Ruby client for Bella Baxter — load secrets into your Ruby or Rails application with zero runtime dependencies.
- 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)
# Gemfile gem "bella_baxter"bundle installrequire "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"]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 encryptiondatabase.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.
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 # 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)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.secretsAlgorithm: ECDH-P256 → HKDF-SHA256 → AES-256-GCM. All operations use Ruby's built-in openssl — no extra gems required.
# 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")Avoid transferring all secret values just to check if anything changed:
v = client.secrets_version puts "Version #{v.version}, last changed #{v.last_modified}"# 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| Sample | Description |
|---|---|
samples/01-standalone/ | Pure Ruby script — all usage patterns |
samples/02-rails/ | Rails integration with database.yml example |
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.rbGenerated 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# 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 automaticallyBecause each method calls ENV.fetch on every invocation (not memoized), secrets injected or refreshed at any point are always current.
| 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 |
- Ruby >= 2.7
- No runtime gem dependencies