# How to manage secrets and environment variables

> A practical guide to setting per-deployment environment variables on Ownkube, marking values secret, and updating them without a rebuild, plus a database URL.

- **Published:** 2026-09-01
- **Author:** Ownkube team
- **Category:** How-To
- **Tags:** secrets, environment-variables, configuration, tutorial, ownkube-compute
- **Canonical URL:** https://ownkube.io/blog/manage-secrets-env-vars
- **Cover:** https://ownkube.io/blog/manage-secrets-env-vars.png

---
Every deployment on Ownkube keeps its own set of [environment variables](https://12factor.net/config), and any value you mark **Secret** is encrypted before it hits disk and masked in the dashboard the moment you save it. This guide covers adding variables, the difference between a runtime variable and a build-time argument, updating a value without triggering a rebuild, and wiring a database connection string into your app.

**TL;DR**

- Environment variables are set per deployment on the **Settings** tab: `KEY=value` pairs, each with a **Secret** toggle.
- Plain values are stored as-is. Secret values are encrypted in Ownkube's database and masked in the UI after you save, so you can rotate them but never read them back.
- Runtime variables are injected into the container when it starts. Saving triggers a rolling update, not a rebuild, so old containers drain once new ones pass health checks.
- Docker build arguments are a separate, build-time input on the deployment's **Builder** tab. They apply on the next build, not the running container.
- [Attach a database](/blog/add-managed-postgres-database) and Ownkube injects `DATABASE_URL` and the individual `PG*` credentials as environment variables automatically. You never type them in by hand.

## How variables are scoped

Variables belong to the deployment, not the environment. `api @ Development` and `api @ Production` keep independent sets, so a value you set on one has no effect on the other. That is deliberate: it keeps a staging API key from ever leaking into production by accident.

Ownkube does not yet have environment-level shared variables that every deployment in an environment inherits automatically. If you run three workers that all need the same `LOG_LEVEL`, you set it on each one today.

## Step 1: open the deployment's Settings tab

Go to the deployment in your [dashboard](https://app.ownkube.io/dashboard) and scroll to **Environment variables**. Every resource type that runs a container (web, [worker](/blog/background-worker-job-queue), [job](/blog/run-scheduled-cron-jobs), function) has this section.

## Step 2: add a key and decide plain or secret

Click **Add variable**. Each row has a **Key**, a **Value**, and a **Secret** toggle.

```bash
NODE_ENV=production
LOG_LEVEL=info
STRIPE_API_KEY=sk_live_...   # flip Secret on this one
```

Use plain text for anything you would not mind seeing in a screenshot: log levels, feature flags, region names. Flip **Secret** on anything else: API keys, tokens, third-party credentials, signing secrets.

If you are creating a fresh deployment rather than editing an existing one, the CLI takes plain variables inline:

```bash
okctl deploy create \
  --name api \
  --image ghcr.io/acme/api \
  --tag v1.4.2 \
  --port 3000 \
  --env NODE_ENV=production \
  --env LOG_LEVEL=info
```

Secret values are best set from the dashboard, where the Secret toggle handles encryption for you.

## Step 3: save

Saving triggers a rolling update. New containers start with the updated environment, and old ones keep serving traffic until the new ones pass their health checks, then drain. There is no separate "apply" step and no downtime window to schedule.

## Step 4: confirm it landed

Open the **Logs** tab and check that your process picked up the new value (a log line at boot, a `/health` response that echoes the version, whatever your app already reports). Secret values are redacted in the log stream regardless of how your app logs them, so you will not see the raw value there either way.

## Build-time arguments versus runtime variables

These are two different inputs and it is worth being precise about which one you need.

A **runtime environment variable** (the ones from Step 2) is injected into the container when it starts. Your process reads it with `process.env`, `os.environ`, or the equivalent. Change it and the next rolling update picks it up, no rebuild required.

A **[Docker build argument](https://docs.docker.com/)** is a separate, build-time-only input on the deployment's **Builder** tab, alongside builder size and the root directory. It feeds an `ARG` in your Dockerfile and only affects the image while it is being built:

```dockerfile
ARG SENTRY_RELEASE
ENV SENTRY_RELEASE=$SENTRY_RELEASE
```

Set `SENTRY_RELEASE=v1.4.2` as a build argument, and it bakes into that build's image. Changing a build argument does not touch the running container. It applies on the deployment's next build, triggered by a new push or by clicking **Rebuild** on the deployment page.

The rule of thumb: if a running process needs to read the value, it is a runtime variable. If only the build step needs it (a version string baked into a bundle, a flag that changes which dependencies get compiled in), it is a build argument.

## Updating a value without a rebuild

This is the common case: rotating a third-party API key. Rotate it at the provider first, then paste the new value into the existing Secret entry on Ownkube and save. That save triggers a rolling update, not a rebuild, so it typically lands in under a minute and the image never changes. Nothing about your container image, your build pipeline, or your deploy history is touched, only the environment the next set of containers starts with.

## Pulling in a database connection string

Attach a database deployment to a web or worker deployment, and Ownkube generates the [connection string](https://www.postgresql.org/docs/current/libpq-connect.html) and injects it as environment variables on that deployment automatically:

```bash
DATABASE_URL=postgresql://user:pass@host:5432/dbname
PGHOST=...
PGPORT=5432
PGUSER=...
PGPASSWORD=...
PGDATABASE=...
```

You do not create these entries yourself and you do not paste a password anywhere. If the database's credentials rotate, the new values are picked up automatically on the next rollout, the same rolling-update path as any other variable change.

## How secrets are actually protected

- **At rest**: encrypted in Ownkube's database and held in the runtime secret store, not in the deployment's plain config.
- **In transit**: only ever travel over TLS.
- **In the UI**: masked after save. A short prefix stays visible so you can tell which value is which without exposing the rest.
- **In logs**: the structured logger redacts any variable with the Secret flag set, so a stray `console.log` of `process.env` will not print it back to you.

Ownkube never shows a saved secret value in plain text again. If you lose track of what you entered, rotate it rather than trying to recover it.

## FAQ

### Can I see a secret value again after I save it?

No. Once saved, a Secret entry is masked permanently in the UI. If you need to confirm a value or hand it to a teammate, rotate it and share the new value out of band.

### Do I need to redeploy manually after changing an environment variable?

No. Saving a runtime variable triggers the rolling update automatically. There is nothing else to trigger.

### If I change a build argument, when does it take effect?

On the deployment's next build, not immediately. Trigger one with a new push, or click **Rebuild** on the deployment page if you want it to apply without waiting for a commit.

### Can I set a variable once and have every deployment in an environment inherit it?

Not yet. Environment-level shared variables are not available today; each deployment keeps its own independent set.

### Are database credentials rotated automatically?

Yes. Ownkube manages database credential rotation, and the new values reach your app as environment variables on the next rollout, with no manual copy-pasting.

## Where Ownkube fits

If you have spent time reconciling a `.env` file against three different dashboards, per-deployment variables with a real Secret flag are a small thing that removes a surprising amount of friction: one place to set a value, one save that rolls out safely, and a database URL you never type by hand. The same deployment, secrets and all, can later move into your own AWS account with no rewrite if you outgrow Ownkube-managed compute; see our [comparison of Railway, Render, and Northflank](/blog/railway-vs-render-vs-northflank-2026) for how that portability compares across platforms, or our breakdown of [self-hosted PaaS options](/blog/self-hosted-paas-comparison-2026) if you are weighing the tradeoffs more broadly. [Deploy your first app](https://app.ownkube.io/login).