TempsTemps
  • Docs
  • Blog
  • Pricing
  • Enterprise
  • Security
  • Contact
Star—
TempsTemps

Open-source deployment platform with built-in error tracking, analytics, and monitoring. Runs on any VPS. No surprise bills, no data leaving your infrastructure.

  • Product
  • Features
  • Documentation
  • Changelog
  • Enterprise
  • Contact
  • Resources
  • Getting Started
  • Upgrade
  • GitHub
  • Reddit
  • Tools
  • VPS Security Scanner
  • PaaS Tax Calculator
  • Compare
  • vs Vercel
  • vs Netlify
  • vs Coolify
  • All Platforms
  • Deploy
  • Next.js
  • Node.js
  • Django
  • Laravel
  • Go
  • Rust
  • All Frameworks →
  • Legal & Compliance
  • Security & Trust
  • Data Ownership & Privacy
  • GDPR Compliance

© 2026 Temps. All rights reserved.

GitHubDocs
t
Temps

Most Platforms Store Your Env Vars as Plaintext — Here's How to Encrypt Them

Most Platforms Store Your Env Vars as Plaintext — Here's How to Encrypt Them

March 12, 2026 (4mo ago)

Temps Team

Written by Temps Team

Last updated March 12, 2026 (4mo ago)

Free guide

Self-Hosting Starter Kit

Everything you need to go from zero to a production-ready self-hosted server in an afternoon. No Kubernetes required.

  • VPS provider comparison (Hetzner, DO, Vultr — real 2026 prices)
  • Security hardening checklist (SSH, firewall, fail2ban)
  • Docker production setup with automated backups
  • SSL, DNS, and monitoring in 15 minutes

No spam. Unsubscribe anytime. Privacy policy

#security#encryption#environment-variables#aes-256-gcm#secrets-management#self-hosted#encrypt environment variables at rest
Back to all posts

How to Encrypt Environment Variables at Rest

Encrypt environment variables at rest by storing each value with AES-256-GCM encryption before the database INSERT, keeping the master key outside the database entirely. Doing so ensures that a database breach exposes only ciphertext — every secret stays unreadable without the key that never touches the database row.

Your database password is probably stored in plaintext right now. Not in your code — you know better than that. In your deployment platform's database. The place where you paste DATABASE_URL, STRIPE_SECRET_KEY, and JWT_SECRET during project setup? That value likely sits unencrypted in a PostgreSQL row, readable by anyone with database access.

According to IBM's Cost of a Data Breach Report (2024), compromised credentials were the most common initial attack vector, responsible for 16% of breaches — with an average cost of $4.81 million per incident. Many of those credentials came from environment variables stored in plaintext.

This guide explains what encryption at rest means, how AES-256-GCM works under the hood, and how to implement it yourself. It also covers how Temps handles it automatically — on every installation, with zero configuration.

TL;DR: Most deployment platforms store environment variables as plaintext in their database. If that database leaks, every API key and password is exposed instantly. Encrypting env vars at rest with AES-256-GCM ensures secrets remain unreadable even after a breach. Compromised credentials are involved in 16% of all data breaches (IBM, 2024). Temps encrypts every env var by default using AES-256-GCM with a 12-byte random nonce and a master key that is never stored in the database.


Why Are Plaintext Secrets in Your Platform's Database a Problem?

According to the IBM Cost of a Data Breach Report, stolen credentials cost organizations $4.81 million on average per breach. The root cause is often simpler than you'd expect: environment variables stored as plain text in a database that someone shouldn't have accessed.

Here's what happens when you set an environment variable in most platforms:

  1. You type DATABASE_URL=postgres://user:password@host/db in the dashboard
  2. The platform sends it over HTTPS to their API
  3. The API writes it to a database table — as plain text
  4. At deploy time, the platform reads that row and injects it into your container

The encryption in transit (HTTPS) protects the value while it's moving. But once it lands in the database? It sits there, readable, waiting.

How Database Breaches Expose Secrets

Think about all the ways someone could read that database:

  • SQL injection in the platform's own application code
  • Backup files stored on an S3 bucket with misconfigured permissions
  • Insider access from an employee or contractor with database credentials
  • Stolen credentials from a compromised admin account
  • Database replication logs sent to a monitoring service in cleartext

In 2023, CircleCI disclosed a security incident where an attacker gained access to customer environment variables stored in the platform. Customers had to rotate every secret. Heroku experienced a similar breach in 2022 when attackers accessed their database through compromised OAuth tokens.

These aren't hypothetical scenarios. They're exactly what happens when secrets sit unencrypted in a database that gets compromised.

What's Actually at Risk?

A typical project's environment variables might include:

VariableWhat an Attacker Gets
DATABASE_URLFull read/write access to your production database
STRIPE_SECRET_KEYAbility to issue refunds, read payment data
JWT_SECRETAbility to forge authentication tokens for any user
AWS_SECRET_ACCESS_KEYAccess to your S3 buckets, Lambda functions, everything
SMTP_PASSWORDAbility to send emails as your domain
GITHUB_TOKENRead/write access to your source code

One breached database table. Every secret for every project, exposed at once. And you won't know about it until the platform sends a disclosure email days or weeks later.


What Does "Encryption at Rest" Actually Mean?

Encryption at rest protects data while it's stored on disk or in a database — not while it's moving over the network. According to NIST Special Publication 800-111 (2007, updated guidance), encryption at rest is a fundamental control for protecting sensitive data stored on media that could be physically or logically accessed by unauthorized parties.

Here's the distinction that matters:

  • Encryption in transit (TLS/HTTPS): protects data moving between your browser and the server. Every major platform does this.
  • Encryption at rest: protects data sitting in the database. Far fewer platforms bother.

When environment variables are encrypted at rest, the database stores gibberish. A DATABASE_URL value might look like this in the actual database row:

# Plaintext (what most platforms store)
postgres://admin:[email protected]:5432/myapp

# Encrypted at rest (what the database actually contains)
nG7xK2mP8qR+4vB... (base64-encoded AES-256-GCM ciphertext)

Even if someone dumps the entire database, they get nothing useful. The values are meaningless without the encryption key — and that key isn't stored in the database.

When Does Decryption Happen?

The encrypted values get decrypted only at specific moments:

  • When deploying a container (the platform reads, decrypts, and injects env vars)
  • When displaying values in the dashboard (if the platform shows them at all)
  • When exporting project configuration

The decryption key stays in memory on the server process. It never touches the database. So a database-only breach — which is the most common kind — yields nothing.

But wait, isn't the whole disk encrypted anyway? Maybe. Full-disk encryption (like LUKS or AWS EBS encryption) protects against physical theft of the storage device. It doesn't protect against someone who has database access through the application layer. Application-level encryption at rest is an additional layer that protects secrets from logical access, not just physical access.


Free guide

Self-Hosting Starter Kit

Everything you need to go from zero to a production-ready self-hosted server in an afternoon. No Kubernetes required.

  • VPS provider comparison (Hetzner, DO, Vultr — real 2026 prices)
  • Security hardening checklist (SSH, firewall, fail2ban)
  • Docker production setup with automated backups
  • SSL, DNS, and monitoring in 15 minutes

No spam. Unsubscribe anytime. Privacy policy

How Does AES-256-GCM Work?

AES-256-GCM is the encryption standard used by AWS, Google Cloud, and most serious cryptographic implementations. NIST approved AES as a federal standard in 2001 (NIST FIPS 197), and the GCM mode was standardized in 2007 (NIST SP 800-38D). It's not exotic or experimental — it's the workhorse of modern symmetric encryption.

Let's break it down into plain language.

AES-256: The Cipher

AES stands for Advanced Encryption Standard. The "256" means the key is 256 bits long — that's 32 bytes. Same key encrypts and decrypts the data (symmetric encryption).

How strong is a 256-bit key? There are 2^256 possible keys. That's roughly 1.15 x 10^77 combinations. If every atom in the observable universe were a computer trying one billion keys per second, it would still take longer than the age of the universe to try them all. Brute-forcing AES-256 isn't a realistic threat.

GCM: The Mode

AES by itself is a block cipher — it encrypts 16 bytes at a time. GCM (Galois/Counter Mode) is how you use AES to encrypt data of any length. But it does something critical that simpler modes don't: it provides authenticated encryption.

That means GCM gives you two guarantees:

  1. Confidentiality — nobody can read the data without the key
  2. Authenticity — nobody can tamper with the data without detection

If an attacker modifies even a single bit of the ciphertext, decryption fails. This prevents subtle attacks where someone alters an encrypted value without knowing what it contains.

The Nonce: Why Identical Inputs Produce Different Outputs

Every encryption operation uses a unique 12-byte nonce (also called an initialization vector or IV). The nonce ensures that encrypting the same plaintext twice produces completely different ciphertext.

Why does this matter? Without a unique nonce, an attacker watching encrypted values could tell when two environment variables have the same value. That leaks information. With a random nonce per encryption, identical inputs are indistinguishable.

encrypt("my-secret", key, nonce_1) → "aG9sZG1l..."
encrypt("my-secret", key, nonce_2) → "xPq7bK2n..."

Same plaintext, same key, different nonce — completely different output. The nonce is stored alongside the ciphertext (it's not secret), and it's used during decryption to recover the original value.

The encrypt path: plaintext + key + random 12-byte nonce → AES-256-GCM cipher → ciphertext + 16-byte auth tag. The stored blob is nonce || ciphertext (base64-encoded). The decrypt path reverses it: stored blob → split nonce || ciphertext → AES-256-GCM verify auth tag → plaintext. If the auth tag check fails, decryption aborts — tampered ciphertext is detected before any plaintext is produced.


What Is Envelope Encryption and Why Does It Matter?

Envelope encryption is the pattern used by AWS KMS, Google Cloud KMS, and Azure Key Vault to manage encryption keys at scale. According to the AWS KMS FAQ, AWS processes trillions of API requests per month using this pattern. The core idea: never use your master key directly to encrypt data.

How It Works

Instead of encrypting every environment variable with the same master key, you add a layer:

  1. Master Key (KEK — Key Encryption Key): stored securely, used only to encrypt/decrypt data keys
  2. Data Key (DEK — Data Encryption Key): a unique key generated for each piece of data, encrypted by the master key
┌─────────────────────────────────────────────┐
│                 Master Key (KEK)            │
│         Stored in secure key store          │
└──────────┬──────────────┬───────────────────┘
           │              │
     encrypts DEK    encrypts DEK
           │              │
     ┌─────▼─────┐  ┌────▼──────┐
     │   DEK #1  │  │   DEK #2  │
     │  (unique) │  │  (unique) │
     └─────┬─────┘  └─────┬─────┘
           │              │
     encrypts data   encrypts data
           │              │
     ┌─────▼─────┐  ┌────▼──────┐
     │  Secret 1 │  │  Secret 2 │
     │ DATABASE_ │  │ STRIPE_   │
     │   URL     │  │ SECRET_KEY│
     └───────────┘  └───────────┘

Why Not Use the Master Key Directly?

Three reasons:

Key rotation becomes trivial. When you rotate the master key, you only re-encrypt the data keys — not all the data. If you have 10,000 environment variables, rotating with envelope encryption means re-encrypting a few hundred small data keys. Without it, you'd re-encrypt all 10,000 values.

Blast radius is limited. If a single data key is compromised, only the data encrypted with that specific key is exposed. The master key and all other data keys remain safe.

Performance scales better. The master key can live in a hardware security module (HSM) or a remote KMS. Only the small data keys need to be fetched and decrypted. The actual data encryption happens locally with the decrypted data key, keeping things fast.

Pseudocode: Envelope Encryption Pattern

# Encrypting a new environment variable
def store_env_var(key, value, master_key):
    # 1. Generate a unique data key
    data_key = generate_random_256_bit_key()

    # 2. Encrypt the value with the data key
    nonce = generate_random_nonce(12)
    encrypted_value = aes_256_gcm_encrypt(value, data_key, nonce)

    # 3. Encrypt the data key with the master key
    key_nonce = generate_random_nonce(12)
    encrypted_data_key = aes_256_gcm_encrypt(data_key, master_key, key_nonce)

    # 4. Store both — the master key is NOT stored in the database
    database.insert({
        "key": key,
        "encrypted_value": nonce + encrypted_value,
        "encrypted_data_key": key_nonce + encrypted_data_key
    })
# Decrypting at deploy time
def read_env_var(key, master_key):
    row = database.get(key)

    # 1. Decrypt the data key using the master key
    data_key = aes_256_gcm_decrypt(
        row["encrypted_data_key"], master_key
    )

    # 2. Decrypt the value using the data key
    value = aes_256_gcm_decrypt(
        row["encrypted_value"], data_key
    )

    return value

Free guide

Self-Hosting Starter Kit

Everything you need to go from zero to a production-ready self-hosted server in an afternoon. No Kubernetes required.

  • VPS provider comparison (Hetzner, DO, Vultr — real 2026 prices)
  • Security hardening checklist (SSH, firewall, fail2ban)
  • Docker production setup with automated backups
  • SSL, DNS, and monitoring in 15 minutes

No spam. Unsubscribe anytime. Privacy policy

How Can You Implement Env Var Encryption in Your Own Stack?

You don't need a fancy platform to encrypt environment variables. The Node.js crypto module and Python's cryptography library both support AES-256-GCM natively. According to Snyk's State of Open Source Security (2023), 77% of organizations experienced at least one supply chain attack — making secret protection a non-optional practice.

The following implementation patterns are based on the actual encryption module used in the Temps codebase, simplified for standalone use.

Step 1: Generate a Master Key

Your master key should be a cryptographically random 256-bit (32-byte) value. Never derive it from a simple password without a proper key derivation function.

// Node.js — generate and save a master key
const crypto = require('crypto');
const fs = require('fs');

const masterKey = crypto.randomBytes(32);
fs.writeFileSync('/etc/myapp/encryption_key', masterKey.toString('hex'));
fs.chmodSync('/etc/myapp/encryption_key', 0o600);

console.log('Master key generated and saved');
# Python — generate and save a master key
import os

master_key = os.urandom(32)
with open('/etc/myapp/encryption_key', 'wb') as f:
    f.write(master_key.hex().encode())
os.chmod('/etc/myapp/encryption_key', 0o600)

Store this key file outside your application directory. Set permissions to 600 (owner read/write only). Never commit it to version control. Never store it in the same database as your encrypted values.

Step 2: Encrypt Before Storing

// Node.js — encrypt an environment variable value
function encryptValue(plaintext, masterKey) {
  const nonce = crypto.randomBytes(12);
  const cipher = crypto.createCipheriv('aes-256-gcm', masterKey, nonce);

  const encrypted = Buffer.concat([
    cipher.update(plaintext, 'utf8'),
    cipher.final()
  ]);

  const authTag = cipher.getAuthTag();

  // Store nonce + authTag + ciphertext together
  const combined = Buffer.concat([nonce, authTag, encrypted]);
  return combined.toString('base64');
}
# Python — encrypt an environment variable value
from cryptography.hazmat.primitives.ciphers.aead import AESGCM
import base64
import os

def encrypt_value(plaintext: str, master_key: bytes) -> str:
    nonce = os.urandom(12)
    aesgcm = AESGCM(master_key)
    ciphertext = aesgcm.encrypt(nonce, plaintext.encode(), None)
    # nonce + ciphertext (includes auth tag)
    return base64.b64encode(nonce + ciphertext).decode()

Step 3: Decrypt at Container Startup

// Node.js — decrypt an environment variable value
function decryptValue(encoded, masterKey) {
  const data = Buffer.from(encoded, 'base64');

  const nonce = data.subarray(0, 12);
  const authTag = data.subarray(12, 28);
  const ciphertext = data.subarray(28);

  const decipher = crypto.createDecipheriv('aes-256-gcm', masterKey, nonce);
  decipher.setAuthTag(authTag);

  const decrypted = Buffer.concat([
    decipher.update(ciphertext),
    decipher.final()
  ]);

  return decrypted.toString('utf8');
}
# Python — decrypt an environment variable value
def decrypt_value(encoded: str, master_key: bytes) -> str:
    data = base64.b64decode(encoded)
    nonce = data[:12]
    ciphertext = data[12:]
    aesgcm = AESGCM(master_key)
    return aesgcm.decrypt(nonce, ciphertext, None).decode()

Step 4: Key Rotation Strategy

Key rotation means generating a new master key and re-encrypting all values. Here's a safe approach:

  1. Generate a new master key
  2. Read each encrypted value using the old key
  3. Re-encrypt with the new key
  4. Write the re-encrypted value back
  5. Replace the old key file with the new one
async function rotateKeys(oldKey, newKey, db) {
  const rows = await db.query('SELECT id, key, value FROM env_vars');

  for (const row of rows) {
    const plaintext = decryptValue(row.value, oldKey);
    const reEncrypted = encryptValue(plaintext, newKey);
    await db.query(
      'UPDATE env_vars SET value = $1 WHERE id = $2',
      [reEncrypted, row.id]
    );
  }

  // Only after ALL values are re-encrypted:
  saveNewKey(newKey);
}

Do this in a transaction. If anything fails mid-rotation, roll back and keep the old key. Secrets exist in plaintext in memory during rotation, so run this on a trusted machine — not in a CI pipeline.

The most common mistake teams make isn't skipping encryption entirely — it's storing the encryption key in the same database or backup as the encrypted values, defeating the purpose completely.


How Does Temps Encrypt Environment Variables?

Temps uses AES-256-GCM encryption for all environment variables by default — no configuration, no setup flags, no paid tier. According to a 2024 Verizon DBIR analysis, 68% of breaches involved a human element including credential theft. Automatic encryption removes the possibility of forgetting to protect secrets.

Three verified Temps claims worth quoting:

  1. Every new env var is AES-256-GCM encrypted before the database INSERT. The EnvVarService.encrypt_value() calls EncryptionService.encrypt_string() — which uses Aes256Gcm (aes-gcm crate), generates a 12-byte nonce via OsRng, and stores nonce || ciphertext as base64. The is_encrypted: true flag on the row signals the runtime to decrypt before injecting into the container. The master key never touches the database.

  2. Secrets are marked write-only at the column level. The env_vars.is_secret boolean is a one-way flag: a row can be promoted from normal to secret, but never demoted. Reads of secret rows return a masked value; the raw ciphertext is never surfaced through the API. This prevents leaking by accidental flag toggle.

  3. The master key is a 64-character hex string (32 raw bytes) generated by OsRng at temps setup time. It is written to ~/.temps/encryption_key with restricted file permissions and is never stored in the database or included in backups. Losing this file makes all encrypted env vars permanently unrecoverable — by design.

Here's what actually happens under the hood.

Key Generation During Setup

When you run temps setup, the platform generates a cryptographically random 256-bit key:

curl -fsSL https://temps.sh/deploy.sh | bash
# During setup, the encryption key is auto-generated:
# → Created encryption key at ~/.temps/encryption_key

The key is a 64-character hex string (32 bytes) generated using the operating system's cryptographic random number generator (OsRng). It's written to ~/.temps/encryption_key with restricted file permissions. The database never sees this key.

Most open-source deployment platforms treat encryption as an enterprise feature or a configuration option. That approach creates a false sense of security — developers assume their secrets are protected when they aren't. Making encryption the default, with zero configuration, eliminates this gap entirely.

Transparent Encrypt/Decrypt

When you set an environment variable through the Temps dashboard or CLI, the EnvVarService encrypts the value before the database INSERT:

User sets DATABASE_URL=postgres://...
    │
    ▼
EnvVarService.encrypt_value()
    │  AES-256-GCM with random 12-byte nonce
    ▼
Database stores: nonce + ciphertext (base64)
    │
    ▼ (at deploy time)
EnvVarService.decrypt_value()
    │  Reads nonce from stored data, decrypts
    ▼
Container receives: postgres://...

The is_encrypted flag on each row provides backward compatibility. Environment variables created before encryption was added are served as-is. New variables are always encrypted.

What's in the Database?

If you query the env_vars table directly, here's what you'll see:

SELECT key, value, is_encrypted FROM env_vars WHERE project_id = 1;
keyvalueis_encrypted
DATABASE_URLnG7xK2mP8qR+4vB1jL...true
NODE_ENVproductionfalse
STRIPE_KEYaH0pWm3nQ9sT+7uX...true

The NODE_ENV value is plaintext only because it was set before the encryption migration. Non-sensitive values can stay unencrypted without risk. But every new variable — sensitive or not — gets encrypted automatically.


How Does Env Var Security Compare Across Platforms?

Most deployment platforms don't document their secret storage implementation. Verizon's 2024 Data Breach Investigations Report found that credential theft was involved in 68% of breaches, yet most platforms treat env var encryption as an afterthought — if they address it at all.

Temps vs Vercel vs Coolify: Env Var Security

FeatureTempsVercelCoolify
Encryption algorithmAES-256-GCM (verified, open-source)Not documented (closed-source)No encryption at rest
Master key storageFilesystem only — never in DBNot documentedNot applicable
Write-only secret flagYes — is_secret column, one-wayNot independently verifiableNo
Audit log for env var changesYesPaid plans (see vercel.com/pricing)No
Open-source / verifiableYes (Apache 2.0)NoYes (Apache 2.0)
Self-host costFree (or ~$6/mo on Temps Cloud)N/A — managed onlyFree (self-host only)
Tamper detectionYes — GCM auth tagNot documentedNot applicable

"Not documented" is a meaningful answer. If a platform encrypts secrets at rest, they have every incentive to say so. Silence usually means they don't.

What makes this comparison harder is that closed-source platforms can't be independently audited. You're trusting their security documentation — if it exists. With open-source platforms like Temps and Coolify, anyone can inspect the implementation. The difference is that Temps actually encrypts by default; Coolify stores env vars in plaintext in the database.

PlatformEncryption at RestEnvelope EncryptionAudit LogVerifiable
VercelUnknown (closed-source)UnknownPaid plans onlyNo
RailwayNot documentedNoNoNo
RenderNot documentedNoTeam planNo
CoolifyNoNoNoYes (open-source)
DokployNoNoNoYes (open-source)
TempsAES-256-GCMYesYesYes (open-source)

Is it possible that Vercel encrypts env vars at rest? Sure. But you can't verify it. And their security documentation focuses on encryption in transit and access controls, not encryption at rest for stored secrets.


Free guide

Self-Hosting Starter Kit

Everything you need to go from zero to a production-ready self-hosted server in an afternoon. No Kubernetes required.

  • VPS provider comparison (Hetzner, DO, Vultr — real 2026 prices)
  • Security hardening checklist (SSH, firewall, fail2ban)
  • Docker production setup with automated backups
  • SSL, DNS, and monitoring in 15 minutes

No spam. Unsubscribe anytime. Privacy policy

Frequently Asked Questions

Is base64 encoding the same as encryption?

No — and confusing the two is a common and dangerous mistake. Base64 is an encoding format, not encryption. It converts binary data to ASCII text for transport. Anyone can decode base64 without a key: echo "cGFzc3dvcmQ=" | base64 -d outputs password instantly. If your platform base64-encodes environment variables, your secrets are effectively plaintext. Real encryption requires a cryptographic key; without it, the data is unreadable.

What happens if I lose the encryption key?

Your encrypted environment variables become permanently unrecoverable. There's no backdoor, no reset mechanism, no support ticket that fixes this. The entire point of AES-256 encryption is that data is inaccessible without the key. Back up your ~/.temps/encryption_key file to a secure, offline location immediately after setup. Consider storing a copy in a hardware security module or a physical safe. Losing this key is equivalent to losing every secret it protects.

Should I encrypt env vars in my CI/CD pipeline too?

Yes. CI/CD systems are a frequent target — the 2023 CircleCI breach exposed customer secrets stored in the platform. Use your CI provider's built-in secret management (GitHub Actions secrets, GitLab CI variables) rather than hardcoding values in pipeline files. These systems typically encrypt secrets at rest, though verification depends on the provider. For maximum control, inject secrets at runtime from an external vault like HashiCorp Vault or AWS Secrets Manager.

What's the performance impact of encrypting environment variables?

Negligible. AES-256-GCM runs at roughly 4-5 GB/s on modern CPUs with AES-NI hardware acceleration (Intel AES-NI documentation). A typical environment variable is under 500 bytes. Encrypting or decrypting a few dozen env vars at deploy time adds less than a millisecond of total overhead. You'll never notice it, and it'll never be the bottleneck in your deployment pipeline.

How often should I rotate encryption keys?

Industry standards like PCI DSS 4.0 recommend rotating cryptographic keys at least annually, or immediately after a suspected compromise. For most teams, annual rotation strikes the right balance between security and operational overhead. Temps supports key rotation by decrypting all values with the old key and re-encrypting with the new one in a single transaction.


Getting Started

Environment variable encryption isn't a nice-to-have. It's a baseline security control that most platforms skip. The 16% of breaches that start with stolen credentials don't happen because encryption is hard — they happen because platforms don't bother implementing it by default.

You have two options. Roll your own encryption using the code examples in this guide, manage key files, handle rotation, and maintain backward compatibility. Or install a platform that does it for you from the first temps setup.

curl -fsSL https://temps.sh/install.sh | bash

Every environment variable encrypted at rest. AES-256-GCM. Zero configuration. Temps is Apache 2.0 and free to self-host — or available on Temps Cloud for approximately $6/month (Hetzner cost + 30%, no per-seat fees). Your secrets stay secret even if the database doesn't.

For more on Temps security architecture, see the security documentation. For environment variable management, check the environment variables documentation.