# SecretSpec 0.20: Git, Docker, inline specs, and five new providers

> Let Git and Docker retrieve credentials from any provider, declare secrets in application code, use the JVM SDK, and deploy on Alpine.

[SecretSpec 0.20](https://github.com/cachix/secretspec/releases/tag/v0.20.0 "SecretSpec 0.20 release") brings SecretSpec-managed credentials to Git and Docker, lets applications declare secrets directly in code, adds five providers, and expands support for JVM and Alpine applications.

This release includes:

* **[Git and Docker credential helpers](#git-and-docker-credential-helpers)**: let ordinary Git and Docker commands retrieve credentials from any SecretSpec provider without copying them into another credential store.
* **[Declarations in application code](#declarations-in-application-code)**: build secret specifications in Rust and prompt for missing values from typed loaders.
* **[libsecretspec and SDKs](#libsecretspec-and-sdks)**: pass inline specifications from eight SDKs, attach caller context, and adopt the new name for `secretspec-ffi`.
* **[Five new providers](#five-new-providers)**: use [Azure App Configuration](/providers/aac/), [Kubernetes](/providers/kubernetes/), [EJSON](/providers/ejson/), [Fly.io](/providers/fly/), and [Cloudflare Secrets Store](/providers/cloudflare/).
* **[JVM and Alpine support](#jvm-and-alpine-support)**: resolve secrets from Java and Kotlin, install static musl CLI builds, and run the Node.js SDK in Alpine images.
* **[Other changes](#other-changes)**: forward container signals, report missing generated secrets correctly, emit shell completions, and make dotenv values round-trip.

## Git and Docker credential helpers

Git and Docker can now read credentials directly from any SecretSpec provider. Configure each helper once, then keep using the usual Git and Docker commands.

### Git credentials

The [Git integration](/integrations/git/) registers a helper for a host, then stores its token through your normal SecretSpec provider:

```bash
$ secretspec git configure \
  --url https://github.com \
  --username YOUR_USERNAME
$ secretspec git login https://github.com
```

After that, normal commands invoke `git-credential-secretspec` automatically:

```bash
$ git clone https://github.com/OWNER/PRIVATE_REPOSITORY.git
$ git push
```

The helper works before a repository has been cloned and can scope credentials by host or URL path. It also supports SMTP credentials for `git send-email`, so the password does not need to live in `sendemail.smtpPass`.

### Docker credentials

The [Docker integration](/integrations/docker/) uses one helper per registry. Configure the registry and store its token separately:

```bash
$ secretspec docker configure \
  --registry ghcr.io \
  --username YOUR_USERNAME
$ secretspec docker login ghcr.io
```

`docker pull`, `docker push`, `docker build`, and Docker Compose can now retrieve the value through `docker-credential-secretspec`:

```bash
$ docker pull ghcr.io/OWNER/IMAGE:TAG
```

Docker’s `config.json` contains only the helper configuration, not the credential value. Separate `DOCKER_CONFIG` directories can use different credentials for the same registry.

The helpers only read credentials. Use `secretspec git login` / `logout` and `secretspec docker login` / `logout` to change stored values. Use `configure` and `unconfigure` to add or remove the helpers themselves.

## Declarations in application code

`secretspec.toml` remains the portable contract shared by the CLI and every SDK. Applications can now define that contract directly when keeping a separate manifest is inconvenient.

### Rust-first specifications

Rust applications can use the new public `Spec`, `Profile`, and `Secret` types to build a specification in code:

```rust
use secretspec::{Profile, Secret, Secrets, Spec};


let spec = Spec::builder("checkout")
    .provider("env", "env://")
    .secret(
        "DATABASE_URL",
        Secret::required("PostgreSQL connection URL").providers(["env"]),
    )
    .profile(
        "production",
        Profile::new().secret(
            "SENTRY_DSN",
            Secret::required("Production Sentry endpoint"),
        ),
    )
    .build()?;


let mut secrets = Secrets::from_spec(spec)?;
secrets.set_profile("production");
let resolved = secrets.resolve()?;
```

Rust-built specs use the same validation as `secretspec.toml`. `Spec::schema_json` generates the same JSON Schema as `secretspec schema`, and `SpecBuilder` can update a file-backed spec without losing comments or formatting.

See [Describing secrets in Rust](/sdk/rust/#describing-secrets-in-rust-020) for the complete builder and format-preserving editing API.

Typed Rust loaders generated by `declare_secrets!` can now call [`prompt_missing()`](/sdk/rust/#interactive-prompting-020) to ask for and store missing required values. Prompting remains opt-in.

## libsecretspec and SDKs

SecretSpec 0.20 gives the SDKs the same new capabilities across languages. It also renames `secretspec-ffi` to [`libsecretspec`](/sdk/overview/). Packaged SDKs handle the rename automatically; update your build only if you link or load `libsecretspec` directly.

### Inline specifications

The [SDKs](/sdk/overview/) for Go, Python, Node.js, Ruby, Haskell, PHP, C#, and Swift can now accept an inline specification. This is useful when the application already owns its configuration or cannot rely on a manifest on disk. For example, in Go:

```go
spec := map[string]any{
  "project": map[string]any{"name": "checkout"},
  "profiles": map[string]any{"default": map[string]any{
    "secrets": map[string]any{
      "API_TOKEN": map[string]any{"description": "API token"},
    },
  }},
}


resolved, err := secretspec.New().
  WithInlineSpec(spec, "/logical/project").
  Load()
```

The base directory tells SecretSpec where to resolve relative provider paths and inherited manifests. Invalid fields and unsupported spec versions fail with an error.

### Caller context

Git and Docker set caller context automatically, recording which tool and operation requested a secret. CLI and SDK callers can provide the same context:

```bash
$ secretspec get GITHUB_TOKEN \
  --caller git \
  --caller-version 2.51.0 \
  --caller-operation credential_get \
  --caller-resource github.com \
  --reason "push release tag"
```

[Audit records](/concepts/audit/) include the caller name, version, operation, and resource. This is separate from [`require_reason`](/reference/configuration/#requiring-a-reason-for-secret-access), so policies can record both which tool accessed a secret and why.

### JVM and Alpine support

The new [JVM SDK](/sdk/jvm/) lets Java, Kotlin, and other JVM applications load SecretSpec secrets directly. Add it with Gradle:

```kotlin
dependencies {
    implementation("org.cachix:secretspec-jvm:0.20.0")
}
```

Then load secrets with the same builder pattern as the other SDKs:

```java
import org.cachix.secretspec.SecretSpec;


try (var resolved = SecretSpec.builder()
    .withProvider("keyring://")
    .withProfile("production")
    .withReason("boot web app")
    .load()) {
    System.out.println(resolved.secret("DATABASE_URL").get());
    resolved.setAsSystemProperties();
}
```

The package supports JDK 11 or newer on Linux, macOS, and Windows, including x64 and Arm64 systems. It includes everything needed to resolve secrets, so the application does not need a separate SecretSpec CLI or Rust toolchain.

The standalone installer and `secretspec-update` now work on Alpine Linux. The Node.js SDK also works directly in images such as `node:alpine`, without a glibc compatibility layer.

## Five new providers

SecretSpec 0.20 adds five providers, bringing the total to 33.

### Azure App Configuration

Use **[Azure App Configuration](/providers/aac/)** to read and manage ordinary key-values or follow references to Azure Key Vault secrets.

```bash
$ az login
$ secretspec set DATABASE_URL --provider aac://payments-production
$ secretspec run --provider aac://payments-production -- ./payments
```

It works with service principals, Azure CLI sessions, managed identity, workload identity, and connection strings. Provider URLs can select labels, prefixes, and tags.

### Kubernetes

**[Kubernetes](/providers/kubernetes/)** stores values in a ConfigMap or Secret using the current kubeconfig context:

```bash
$ secretspec set DATABASE_URL \
  --provider k8s+secret://app-credentials@production
$ secretspec run \
  --provider k8s+secret://app-credentials@production \
  -- ./application
```

You can use convention keys or point declarations at existing `.data` entries. The provider supports reads, writes, deletion, and declaration discovery.

### EJSON

**[Shopify’s EJSON](https://github.com/Shopify/ejson)** lets teams keep encrypted secrets alongside application source. Secret values are encrypted with a public key and decrypted with the matching private key, while the JSON structure stays visible.

SecretSpec’s [EJSON provider](/providers/ejson/) reads string values from those files. A `ref.item` selects any RFC 6901 JSON Pointer, while convention addresses use `/{project}/{profile}/{key}`.

Supply the EJSON private key as a SecretSpec [provider credential](/reference/provider-credentials/). It can come from Google Cloud Secret Manager, the system keyring, or any other readable provider. The EJSON provider is read-only.

### Fly.io

Use **[Fly.io](/providers/fly/)** to publish and delete application secrets with `flyctl`. Secret values stay out of process arguments, and app-scoped deploy tokens can come from provider credentials. Fly.io does not return plaintext, so keep the original value in a readable provider and use `fly` as the deployment destination.

### Cloudflare Secrets Store

Use **[Cloudflare Secrets Store](/providers/cloudflare/)** to publish and delete account-level secrets through Cloudflare’s API. Authenticate with a scoped API token from another provider or an existing Wrangler session. Cloudflare does not return plaintext, so keep the original value in a readable provider and use `cloudflare` as the deployment destination.

## Other changes

Shell completions now come directly from the CLI definition:

```bash
$ source <(secretspec completions bash)
```

Bash, Elvish, Fish, Nushell, PowerShell, and Zsh now complete commands, profiles, scopes, secret names, providers, files, and executables. Completion does not read secret values. See [`secretspec completions`](/reference/cli/#completions-020) for persistent installation instructions ([#330](https://github.com/cachix/secretspec/pull/330)).

0.20 also includes these CLI and automation changes:

* On Unix, `secretspec run` forwards `SIGTERM`, `SIGINT`, and `SIGHUP` to its child, so containers can shut down gracefully ([#391](https://github.com/cachix/secretspec/pull/391)).
* `check --json`, `check --explain`, and SDK reports now mark an unprovisioned required [`generate` declaration](/concepts/generation/) as `missing_required` instead of resolved. Run `secretspec check` or `secretspec run` once to generate and store it ([#394](https://github.com/cachix/secretspec/pull/394)).
* Human-readable `secretspec check` output moves to stdout, matching its JSON and explain modes. Diagnostics remain on stderr ([#372](https://github.com/cachix/secretspec/issues/372)).
* Dotenv files, age-encrypted dotenv blobs, and `export --format dotenv` now use dotenv-ng. Dollar signs and bcrypt-style values now round-trip correctly ([#73](https://github.com/cachix/secretspec/issues/73)).
* [`extract`](/reference/configuration/#structured-extraction-019) supports INI documents, selecting unsectioned keys with `/key` and named-section keys with `/section/key` ([#386](https://github.com/cachix/secretspec/pull/386)).
* The [age provider](/providers/age/) can now delete secrets, enabling `secretspec delete`, `import --delete-source`, and age-backed provider caches ([#328](https://github.com/cachix/secretspec/pull/328)).
* Closing a stdout pipe is quiet on Unix, so commands such as `secretspec export | head` behave like other Unix tools ([#377](https://github.com/cachix/secretspec/pull/377)).

Provider-specific fixes keep 1Password batches fast when optional items are missing ([#401](https://github.com/cachix/secretspec/pull/401)), prevent Bitwarden convention names from colliding across projects and profiles ([#390](https://github.com/cachix/secretspec/pull/390)), make Infisical Universal Auth sessions more reliable ([#402](https://github.com/cachix/secretspec/pull/402)), and let Node.js applications exit cleanly after AWS resolution ([#365](https://github.com/cachix/secretspec/pull/365)).

## Upgrading

```bash
$ cargo install secretspec
```

For most users, 0.20 is a drop-in upgrade. The new integrations, providers, and inline declarations are opt-in. Check these cases before upgrading:

* scripts that captured human-readable `secretspec check` output from stderr should capture stdout;
* Google Cloud Secret Manager writes now use the collision-safe `secretspec2--{project}--{profile}--{key}` convention. Reads fall back to the matching 0.19 name until a write moves the value, leaving the old secret in place for rollback;
* Bitwarden Password Manager convention items now use `secretspec/{project}/{profile}/{key}` titles. `init --from bw://` preserves references to legacy bare items so they can keep working or be migrated deliberately.

Rust applications using `secretspec-derive` can remove direct `serde` or `secrecy` dependencies if they were needed only for generated types. Code using the old raw configuration or code-generation APIs should move to `Spec`, `SpecBuilder`, `Profile`, and `Secret`.

See the [full changelog](https://github.com/cachix/secretspec/blob/main/CHANGELOG.md) for every change and fix in this release.

## What’s next

### Resolver and provider IPC

The resolver and provider IPC protocols are currently RFCs in [PR #362](https://github.com/cachix/secretspec/pull/362). The [resolver protocol](https://feat-ipc-v1-secretspec.domen.workers.dev/reference/resolver-protocol/) would let applications request individual secrets from `secretspec serve`. The [provider protocol](https://feat-ipc-v1-secretspec.domen.workers.dev/reference/provider-protocol/) would let external executables act as SecretSpec providers. Both are targeted for 0.21 and may change during review.

### Nix secret-provider plumbing

Work on the Nix integration identified that SecretSpec first needs the IPC layer above, so the concrete integration is blocked until 0.21 is released. In the meantime, the underlying refactoring has been split into a [generic secret-provider interface for Nix](https://github.com/NixOS/nix/pull/16339).

### devenv machines

Separately, [devenv’s experimental `machines` interface](https://github.com/cachix/devenv/pull/3073) aims to handle NixOS installation, NixOS and nix-darwin deployment, and home-manager activation in one workflow. Its SecretSpec integration can supply deployment credentials without putting their values in Nix evaluation or store paths. This work follows devenv’s own release schedule and is not part of SecretSpec 0.21.

Questions or feedback? Join us on [Discord](https://discord.gg/naMgvexb6q).

## Fencer for Open Source

In partnership with SecretSpec, [Fencer has expanded its offering to open source](https://www.fencer.dev/blog/introducing-fencer-for-open-source). Public repositories now get free static analysis, dependency scanning, secret scanning, and GitHub configuration scanning, with no credit card and no expiry.

We recommend Fencer for code security scanning, and Fencer recommends SecretSpec for secrets management. Fencer finds credentials and other risks already in a repository, while SecretSpec helps keep the next secret in the right provider instead of source code or a dotenv file.