# onepassword-operator: syncing Kubernetes secrets from 1Password

> The 1Password Connect Kubernetes Operator is a controller that turns a 1Password item into a Kubernetes secret, and keeps it in step when the item changes. The interesting parts are the custom resource it introduces, the two ways of deploying it with Connect through a single Helm command, and the auto-restart behaviour that solves the problem of a workload holding a secret it read at start-up.

**1Password/onepassword-operator** — The 1Password Connect Kubernetes Operator provides the ability to integrate Kubernetes Secrets with 1Password. The operator also handles autorestarting deployments when 1Password items are updated.

- Repository: https://github.com/1Password/onepassword-operator
- Website: https://www.1password.dev/connect/
- Stars: 624 · Forks: 72
- Language: Go
- License: MIT
- Published: 2026-10-22 · Updated: 2026-10-22 · Language: en
- Canonical page: https://hysenlabs.com/projects/1password-onepassword-operator

## What the operator is for

The description is two sentences and it names both halves of the job. The 1Password Connect Kubernetes Operator provides the ability to integrate Kubernetes secrets with 1Password, and the operator also handles auto-restarting deployments when 1Password items are updated.

The first half is the obvious one and it is still worth stating precisely, because there is more than one way to do it. A Kubernetes secret is a base64 encoded object in the cluster. Traditionally you create it by hand, or you commit an encrypted file and a decryption step in the pipeline, or you put the value in the CI system's own secret store. All three put a copy of the credential somewhere other than the password manager, and all three need a process to keep that copy up to date.

This operator removes the copy. You declare that a particular 1Password item should become a particular Kubernetes secret, and the operator fetches the item through Connect and writes the secret.

The second half is the one that shows the author has thought about how this fails in practice. Kubernetes does not update an environment variable in a running pod. A secret that changes on disk is not a secret that a process has re-read. So the operator watches for changes to the underlying item and restarts the affected deployments, which is the only way to make a new credential take effect in a workload that read the old one at start-up.

That second feature is what turns this from a convenience into something you can rely on. A synchronisation that leaves stale values in running processes is a synchronisation that gives false confidence, and the operator closes that gap rather than leaving it to you.

The project is MIT licensed, and the readme adds the caveat that use of the 1Password APIs and services accessed through these tools is governed by the 1Password API terms of service, which is the right thing for a project that sits between an open source operator and a commercial service.

The last push was on 2026-10-02 and the releases are in the 1.x line, with 1.12.0 in March 2026.

## The custom resource, and the list form

The operator introduces a custom resource, and its shape is what you write to make a secret appear.

```yaml
apiVersion: onepassword.com/v1
kind: OnePasswordItem
metadata:
  name: <item_name> #this name will also be used for naming the generated kubernetes secret
spec:
  itemPath: "vaults/<vault_id_or_title>/items/<item_id_or_title>"
```

There are three things to read here. The group and version define a new API, and the kind is the object you create. The metadata name does double duty, because the readme notes in the comment that it is also used for naming the generated Kubernetes secret. And the spec is a single field, an item path, with a path template that names a vault and an item inside it.

That path template accepts either an identifier or a title at each level, which is the decision that matters in practice. An identifier is stable and a title is not, so a path built from titles breaks silently when somebody renames a vault, and a path built from identifiers is unreadable. Accepting both means you can use titles in a small team where everybody knows the vault names, and identifiers in a large one where the names change.

There is also a list form, for when one file should produce several secrets.

```yaml
apiVersion: onepassword.com/v1
kind: OnePasswordItemList
items:
  - metadata:
      name: <item_name_1> #this name will be used for naming one of the generated kubernetes secrets
    spec:
      itemPath: "vaults/<vault_id_or_title>/items/<item_id_or_title>"
  - metadata:
      name: <item_name_2> #this name will also be used for naming another of the generated kubernetes secrets
    spec:
      itemPath: "vaults/<vault_id_or_title>/items/<item_id_or_title>"
```

The list kind carries a list of the same item structure, and each entry has its own name, which becomes the name of its own generated secret. So a single manifest can produce a whole set of secrets, one per 1Password item, and the naming convention is uniform: the resource name is the secret name.

That convention has a consequence worth thinking about before you write a hundred of these. A Kubernetes secret name has to be a valid object name, so the name you choose for the resource constrains the name of the secret, and the name of the secret is what your deployment refers to. Choosing names that read well in both places is worth doing once rather than renaming later.

## Deploying it, with Connect, in one command

The quickstart has three steps, and the second one is where the design shows.

First you add the 1Password Helm chart to your repository. Then you run a single command that installs Connect, the service that talks to 1Password, and the operator itself, together.

```
helm install connect 1password/connect --set-file connect.credentials=1password-credentials-demo.json --set operator.create=true --set operator.token.value = <your connect token>
```

Three details are worth pulling out of that line. The credentials are passed as a file rather than as an inline value, which keeps the service account token out of the shell history and out of the release metadata. The operator is created by a flag rather than installed as a second chart, which means one release contains both halves of the system. And the operator gets the Connect token as a value.

Then you apply your item manifest and check the result.

```
kubectl apply -f <your_item>.yaml
```

```
kubectl get secret <secret_name>
```

The whole flow is three commands, which is what you want from an infrastructure component. There is a separate usage guide in the repository for the details of how to deploy and use the operator, and the readme points to it rather than trying to cover the full configuration in the quickstart.

The reason the operator needs a token of its own, rather than just reading the credentials file, is that the two run in different places. Connect is a service that talks to 1Password on the network, and the operator is a controller in the cluster that talks to Connect. The token is what lets the operator authenticate to Connect, and it is scoped to the Connect instance rather than to 1Password itself.

The deeper design point is that 1Password is not reachable from inside most clusters. The corporate network policy that keeps the cluster isolated also keeps the password manager out of it. Connect solves that by being the thing that has the connectivity, and the operator solves it by only needing to talk to Connect, which can sit inside the same network as the cluster. So the topology is: the operator inside the cluster, Connect beside it, 1Password outside.

## How the operator is built

The repository layout and the module file describe a conventional Kubernetes operator, and the conventions are the interesting part.

The module path is under the 1Password organisation, and it requires a recent Go with a matching toolchain directive. The dependencies split into two groups that say a lot about the design. On one side there is the Connect SDK for Go, the newer 1Password SDK, and a small logging package. On the other side there is the Kubernetes machinery: the API types, the API machinery, the client, the kubectl library, and the controller runtime.

The presence of both 1Password SDKs is worth a note. The Connect SDK is the older client for the Connect API, and the newer SDK is the general one. Having both in the dependency list suggests the operator is in the middle of a migration, or that it supports two paths to the same data, and either way it is a sign of a project that has been around long enough to have accumulated both.

The core Kubernetes dependencies are pinned to the same minor version, which is the right thing to do: an operator that mixes API machinery versions ends up with conversion problems between the custom resource and the built-in types.

The testing stack is a mix, and the mix is itself informative. Ginkgo with the matching matcher library is the framework for the controller and integration style tests, and the test helper library is a separate Go module, referenced through a replace directive, so the same helper code can be shared with the other 1Password operator projects. The assertion library is also there for the simpler unit tests. The replace directive is called out in a comment in the module file, which tells you it is deliberate rather than an accident of a local checkout.

The repository layout follows the standard operator shape. There is an api directory for the custom resource types, a cmd directory with the entry point, an internal controller directory, a pkg directory, a config directory for the deployment manifests, a docs directory, a hack directory for the usual scripts, a test directory, a version package, and a project file describing the operator to the tooling that generates the manifests.

## The container image, and the two details in it

The Dockerfile is short, and two decisions in it are worth explaining.

The first is the base image of the final stage, which is the distroless static image running as a non-root user. Distroless means the image contains the binary and essentially nothing else: no shell, no package manager, no distribution files. The readme points at the Google container tools project for the details, and the practical effect is an image with a very small attack surface, which is the right posture for a component that holds a token that can read passwords.

The second is the multi-stage build, and the comments in it are unusually explanatory. The first stage copies the module manifests, then the test helper module, then downloads the dependencies, and only then copies the source. That ordering is the standard trick to keep the dependency download layer cached when the source changes.

The comment about the architecture arguments is the part that is genuinely instructive. The architecture variable has no default value, on purpose, so that the binary is built for the platform where the build command was called rather than for the platform of the builder. The comment gives the reason: a local build on an Apple Silicon machine produces a container for the arm architecture, and an x86 machine produces one for amd64, so leaving the value empty ensures the container and the binary shipped on it have the same platform.

That is the kind of comment that only gets written after somebody has shipped a broken image. The compile step turns off the C cross compilation, which produces a static binary, and the linker flags stamp the operator version into it from a build argument.

The entry point is the manager binary, and the config directory for the Connect service is copied into the final image alongside it.

## Releasing, and the two channels of the bundle

The make file is the standard operator build file, and the parts worth reading are the ones that describe the release process rather than the compile.

The version is a variable that defaults to the current release, and the comments explain the two ways to override it for a specific build: pass it as an argument to the bundle target, or export it in the environment. That is a small piece of ceremony, and it exists because the version has to be the same in the bundle metadata and in the image tag, and the two are generated at different points.

Then there are the channels. The bundle channels are a variable that names which channels the bundle is published to, with an example of adding a new one. The default channel is a separate variable, and the readme explains that you regenerate a bundle for another default channel by overriding it.

The two together are the release channel model, and it is the part of the make file that tells you how the project actually ships. A bundle can be published to several channels at once, and one of them is the default that a plain install gets. That is a sensible arrangement for an operator, because a cluster that installs the operator gets whatever the default channel points at, and the maintainer decides when a new version becomes the default.

The deployment name is a variable too, with a comment saying it should be the same as in the manager manifest, which is the kind of duplication that operator projects accumulate and that a variable at least makes visible.

The rest of the repository supports the release process. There is a version file at the top level, a change log, a contributing guide, a development container configuration, a linter configuration, a directory of scripts, and a Dockerfile. The readme finishes with the support routes: an issue tracker for bugs and feature requests, a developer Slack workspace, and a developer newsletter. And there is a security section that asks for responsible disclosure, with a bug bounty address, which for a component that sits in the path of every secret in a cluster is the right thing to have.

## Conclusion

onepassword-operator fits a team that keeps credentials in 1Password and wants them in a cluster without copying them by hand or storing them in the cluster's own secret store. It does not fit an air-gapped cluster with no route to 1Password, and it does not fit a team that wants the operator to manage the whole secret lifecycle rather than the synchronisation part. Verify first that the cluster can reach the Connect service and that the Connect token has the vault access you need, that the custom resource names are valid Kubernetes secret names, because the generated secret takes the resource name, and that you understand what the operator does on an item change, since the restart behaviour is what makes an updated secret take effect in a running workload.

## FAQ

### What does the 1Password Connect Kubernetes Operator do?

It integrates Kubernetes secrets with 1Password. You declare a OnePasswordItem custom resource naming a vault and an item, and the operator creates a Kubernetes secret from that item through 1Password Connect, and it restarts the affected deployments when the underlying item is updated so that a running workload picks up the new value.

### How do I install the onepassword-operator?

Add the 1Password Helm chart to your repository and run one helm install command that creates both the Connect service and the operator, passing the Connect credentials as a file and the Connect token as a value. Then apply a manifest with a OnePasswordItem resource and check the generated secret with kubectl get secret.

### What is the OnePasswordItem custom resource?

A resource in the onepassword.com group with a single spec field, an item path, and a metadata name. The path names a vault and an item, accepting either an identifier or a title at each level, and the metadata name is also used as the name of the generated Kubernetes secret. A list variant exists for creating several secrets from one manifest.

### How does the onepassword-operator reach 1Password from inside a cluster?

It does not talk to 1Password directly. The Connect service runs alongside the operator and holds the connectivity and the credentials, and the operator authenticates to Connect with its own token. That arrangement is what lets the operator work in a cluster that has no route to the password manager.

### How is the onepassword-operator released?

Through bundle channels, with the make file carrying a variable for the channels a bundle is published to and a separate variable for the default channel, and a version variable that can be overridden per build. The container image is built in stages and ships on the distroless static base as a non-root user, with the operator version stamped into the binary at link time.

## Sources

- [1Password/onepassword-operator on GitHub](https://github.com/1Password/onepassword-operator)
- [License: MIT](https://github.com/1Password/onepassword-operator/blob/main/LICENSE)
- [Project website](https://www.1password.dev/connect/)
- [README](https://github.com/1Password/onepassword-operator/blob/main/README.md)
- [Releases](https://github.com/1Password/onepassword-operator/releases)

---

Hysen Labs editorial analysis, written from the project's own repository and release notes. Cite the canonical page: https://hysenlabs.com/projects/1password-onepassword-operator
