Configuring Apicurio Registry with GitOps storage
Configure Apicurio Registry to use a Git repository as the source of registry data. With GitOps storage, you manage schemas and API designs declaratively in Git, and all changes flow through Git commits instead of REST API calls.
Prerequisites
-
You have a way to run the Apicurio Registry container image, for example Docker, Podman, or a Kubernetes cluster.
-
You have a Git repository for registry data.
Overview of GitOps storage
GitOps storage is a read-only storage variant that loads registry data from a Git repository. You declare the desired state of the registry in files, and the registry serves that state, so all changes flow through your Git workflow with reviews, history, and rollbacks.
GitOps storage is an experimental feature. You must enable the experimental features gate (APICURIO_FEATURES_EXPERIMENTAL_ENABLED=true) to use it. The data format (*-v0) might change in future releases.
|
Architecture
GitOps storage uses a polling-based architecture with blue-green database switching:
-
The registry reads a local Git clone on a mounted volume. The registry never fetches from remote repositories itself; a sync container or an external process updates the clone.
-
The registry periodically checks the current commit of the clone. A debounce mechanism absorbs rapid sequences of pushes before a load starts.
-
When the commit changes, the registry loads all data into an inactive in-memory database and validates it.
-
After a successful load, the active and inactive databases switch atomically. If the load fails, the registry keeps serving the last successfully loaded data.
Data flows in one direction only: from Git to the registry. REST API calls that would modify data are rejected with an HTTP 409 response. The registry requires no external database, because data is loaded into in-memory storage from Git.
Relationship of repositories and registry instances
One repository can serve multiple registry instances, and one registry instance can aggregate multiple repositories. Each metadata file can declare which registry instances load it, and the registry loads only the data that references its own identifier.
When to use GitOps storage
GitOps storage is a good fit when:
-
You want schema and API changes to go through pull requests, reviews, and CI checks
-
You need a read-only registry where all changes flow through a Git workflow
-
You want to avoid operating an external database
-
You already manage configuration as code and want registry content to follow the same model
GitOps repository data format
A GitOps repository contains metadata files that describe registry content, and plain schema files that hold the content itself. This section describes the file types, their discovery, and their fields.
No fixed directory structure is required. The registry discovers metadata files anywhere in the repository by file name suffix. By default, files ending in .registry or .ar, combined with the .yaml, .yml, or .json extensions, are treated as metadata files, for example order-event.registry.yaml. You can change the suffixes with the apicurio.polling-storage.file-suffixes property.
Each metadata file declares its kind with a $type discriminator:
| Type | Purpose and main fields |
|---|---|
|
Registry configuration. Fields: |
|
An artifact group. Fields: |
|
An artifact with its versions declared inline. Fields: |
|
Optional content metadata, for explicit content identifiers and references. |
The following example declares a registry configuration, a group, and an artifact. All three files are required: a load that references an undeclared group fails, and the registry does not become ready.
$type: registry-v0
registryId: main
globalRules:
- ruleType: VALIDITY
config: FULL
$type: group-v0
groupId: my-schemas
$type: artifact-v0
groupId: my-schemas
artifactId: order-event
artifactType: AVRO
versions:
- version: "1.0.0"
state: ENABLED
content: order-event-1.0.0.avsc
Content files are plain schema files that metadata files reference by a path relative to the directory of the metadata file. The artifact type comes from the artifactType field of the artifact metadata, or from automatic content detection when you omit the field. The file extension determines only the media type that the registry stores with the content:
| Extension | Stored media type |
|---|---|
|
YAML |
|
XML |
|
Protobuf |
|
GraphQL |
|
Thrift |
All other extensions |
JSON |
The following additional rules apply:
-
If
registryIdsis omitted or empty on a group or artifact, any registry instance loads it. -
Timestamps such as
createdOnare optional. When omitted, the Git commit time is used. Supported formats are ISO 8601, with or without a time zone or as a date only, and Unix milliseconds. -
The optional
validatedUpTofield on an artifact controls rule validation during a load. When unset, only the latest version is validated, against all preceding versions. When set to a version identifier, versions up to and including that version are not re-validated. When the value matches no version, all versions are validated.
Configuring GitOps storage with a single repository
You can configure Apicurio Registry to load its data from a single Git repository that is available as a local clone on a mounted volume.
-
You have a Git repository that contains a registry configuration file (
registry-v0) and registry content. -
You have a local clone of the repository, or a sync container that maintains one.
-
In your repository, verify that a registry configuration file exists and that its
registryIdvalue matches the registry instance identifier that you configure in the next step. The default identifier ismain. -
Configure the registry with the following environment variables:
APICURIO_STORAGE_KIND=gitops APICURIO_FEATURES_EXPERIMENTAL_ENABLED=true APICURIO_POLLING_STORAGE_ID=main -
Mount the local clone of the repository into the registry container at
/repos/default. A read-only mount is sufficient for serving content, because the registry does not write to the repository. If you use dry-run validation, mount the workspace writable instead, because the registry writes validation request files under the workspace directory. The mount path is the workspace directory (apicurio.gitops.workspace, default/repos) combined with the repository directory name (apicurio.gitops.repo.dir, defaultdefault).For example, with Docker Compose:
volumes: - ./example-repo:/repos/default:ro -
Optional: To read from a branch other than
main, set theAPICURIO_GITOPS_REPO_BRANCHenvironment variable. If the configured branch does not exist in the clone, the registry falls back toHEAD. -
Start the registry.
-
Verify the synchronization status:
$ curl http://localhost:8080/apis/registry/v3/admin/gitops/statusThe registry reports not ready on its readiness probe until the first load succeeds. After the first successful load, it stays ready even if a later load fails, and it keeps serving the last successfully loaded data. With the default poll and debounce settings, expect a new commit to become visible after roughly 20 to 30 seconds.
Configuring the GitOps sync container
The registry reads only local Git clones and never contacts remote repositories. You can run the GitOps sync container next to the registry to keep a local clone up to date with a remote repository, or to accept pushes directly.
-
You have configured the registry for GitOps storage.
-
For pull mode, you have the URL of a remote Git repository.
-
Run the
quay.io/apicurio/apicurio-registry-gitops-synccontainer so that it shares a volume with the registry. Mount the shared volume at/reposin both containers, read-only in the registry container. -
For pull mode, which is the default, set the remote repository URL:
APICURIO_GITOPS_REPO_URL=https://github.com/my-org/my-registry-data.gitThe sync container clones the repository and then updates it every 30 seconds. You can change the interval with the
APICURIO_GITOPS_PULL_INTERVALenvironment variable. -
Optional: For SSH remotes, provide the private key and the known hosts file:
APICURIO_GITOPS_PULL_SSH_KEYS=/etc/gitops/ssh/id_ed25519 APICURIO_GITOPS_PULL_SSH_KNOWN_HOSTS=/etc/gitops/ssh/known_hosts -
Optional: Adjust the security level with the
APICURIO_GITOPS_SECURITYenvironment variable. The default level,strict, rejects loose key file permissions, and fails at startup when an SSH remote is configured without any SSH keys. When you provide keys without a known hosts file, the container accepts a host key on first connection. Thedevlevel relaxes host key checking for development. Plaintexthttp://remote URLs are rejected at both levels. -
Optional: To accept Git pushes instead of pulling from a remote, set
APICURIO_GITOPS_MODE=push. The sync container runs a restricted SSH server for thegituser on port2222. Configure the authorized client keys and the host key:APICURIO_GITOPS_PUSH_SSH_AUTHORIZED_KEYS=/etc/gitops/ssh/authorized_keys APICURIO_GITOPS_PUSH_SSH_HOST_KEY=/etc/gitops/ssh/host_keyClients then push to the container directly:
$ git push ssh://git@registry-host:2222/repos/defaultIn push mode, any authorized push user can access all Git repositories on the shared volume.
-
Optional: When you configure multiple repositories, set the mode per repository with
APICURIO_GITOPS_REPOS_N_MODE, which overrides the globalAPICURIO_GITOPS_MODEsetting.
Configuring multiple GitOps repositories
You can configure one registry instance to aggregate data from multiple Git repositories, for example one repository per team. The registry detects conflicting artifact definitions across repositories and rejects them.
-
You have configured the registry for GitOps storage.
-
Configure the repositories on the registry with indexed environment variables. Indexes must start at 0 and be consecutive:
APICURIO_GITOPS_REPOS_0_DIR=platform APICURIO_GITOPS_REPOS_1_DIR=fulfillment APICURIO_GITOPS_REPOS_1_BRANCH=fulfillmentOptional: Assign an identifier for status reporting with
APICURIO_GITOPS_REPOS_N_ID. The default identifier is the directory name.Do not combine the single-repository variables (
APICURIO_GITOPS_REPO_DIR,APICURIO_GITOPS_REPO_BRANCH) with indexed variables. The registry rejects mixed configuration, index gaps, duplicate repository identifiers, and duplicate directory and branch combinations at startup. -
If you use the sync container, configure the remote URL for each repository with
APICURIO_GITOPS_REPOS_N_URL, and optionally per-repository SSH keys withAPICURIO_GITOPS_REPOS_N_SSH_KEYS. -
Ensure that each artifact is defined in exactly one repository. If the same
groupIdandartifactIdcombination is defined in more than one repository, the registry reports all conflicts and rejects the entire load. -
Verify the aggregated state:
$ curl http://localhost:8080/apis/registry/v3/admin/gitops/statusThe
sourcesfield maps each repository identifier to the currently loaded commit.By default, the registry labels each loaded group and artifact with the identifier of its source repository, using the
system:sourcelabel key. You can change or disable this with theapicurio.polling-storage.source-label-keyproperty.
Validating repository changes before merging
You can validate a Git reference, such as a pull request branch, against the full registry validation pipeline without affecting the data that the registry serves. This enables CI checks that reject invalid schema changes before they merge.
-
Validation is enabled on the registry (
apicurio.gitops.validate.enabled, defaulttrue). When validation is disabled, the validation endpoints return an HTTP503response. -
The sync container is running, because it fetches the reference to validate.
-
The workspace volume is writable by the registry, because the registry writes validation request files under the workspace directory.
-
Create a validation task for the reference:
$ curl -X POST -H "Content-Type: application/json" \ -d '{"type": "pull", "repoId": "platform", "ref": "refs/pull/42/head"}' \ http://localhost:8080/apis/registry/v3/admin/gitops/validateThe
repoIdvalue must match the directory name of a configured repository; identifiers that you assign withAPICURIO_GITOPS_REPOS_N_IDare not recognized by the sync container. An unknown value fails the created task instead of rejecting the request. The response contains ataskId. -
Poll the task until it completes:
$ curl http://localhost:8080/apis/registry/v3/admin/gitops/validate/TASK_IDThe task moves through the states
pending,submitted,fetching, andvalidatingtocompletedorfailed. -
Check the outcome of a completed task in the
resultfield, which issuccessorfailure. A successful task reports group, artifact, and version counts. A failed validation reports structured errors with adetailmessage and, where available, thesourcerepository and thecontextfile path. -
Optional: Delete a task that you no longer need:
$ curl -X DELETE http://localhost:8080/apis/registry/v3/admin/gitops/validate/TASK_IDTasks are removed automatically after a time to live, which is one hour by default. To limit disk usage, at most five tasks hold disk resources at the same time by default; further requests are queued.
GitOps storage configuration properties
The following tables describe the configuration properties of the registry for GitOps storage, and the environment variables of the GitOps sync container.
All GitOps storage properties are experimental and require apicurio.features.experimental.enabled=true.
| Property (environment variable) | Default | Description |
|---|---|---|
|
|
Set to |
|
|
Base directory where Git repositories are mounted. Repository directories are resolved relative to this path. |
|
|
Directory name of the Git repository, relative to the workspace. Single-repository configuration. |
|
|
Branch to read from. Single-repository configuration. |
|
No default |
Directory name for repository |
|
|
Branch for repository |
|
Directory name |
Identifier for repository |
|
|
Enable the dry-run validation endpoints. When disabled, the validation endpoints return an HTTP |
|
|
Time to live for validation tasks. Tasks are removed automatically after this period. |
|
|
Maximum number of validation tasks that hold disk resources at the same time. Further requests are queued. |
|
|
Identifier of this registry instance. Only data referencing this identifier is loaded. Must match the |
|
|
Generate content and global identifiers deterministically from content hashes and coordinates when they are not specified in the data. |
|
|
Minimum period between checks of the data source. |
|
|
Wait until no new changes arrive for this duration before loading. |
|
|
Maximum time to wait for changes to settle before a load is forced. |
|
|
Suffixes that identify metadata files, combined with the |
|
|
Reject a load when no registry configuration file matches the configured registry identifier. This guards against accidental data loss from force pushes or empty branches. |
|
|
Label key used to tag loaded groups and artifacts with the identifier of their source repository. An empty value disables source labels. |
| Environment variable | Default | Description |
|---|---|---|
|
|
Global synchronization mode: |
|
No default |
Remote repository URL for pull mode. Single-repository configuration. |
|
No default |
Remote repository URL for repository |
|
Global mode |
Per-repository mode override: |
|
No default |
Per-repository SSH private keys, added alongside the global keys. |
|
|
Interval between remote updates, in seconds. |
|
|
Clone depth. |
|
No default |
Path to one or more SSH private keys for pull mode, comma-separated. The keys are tried in order. |
|
No default |
Path to a known hosts file for pull mode. |
|
|
SSH server port for push mode. |
|
No default |
Path to the authorized keys file for push mode. |
|
No default |
Path to the SSH host key for push mode. |
|
|
Security level: |
|
|
Enable the validation watch loop in the sync container. |
|
|
Interval at which the sync container checks for validation requests. |
|
|
Age after which stale validation checkouts are removed. |
|
|
Timeout for fetching a Git reference during validation. |
GitOps management API
The registry provides administrative REST endpoints for GitOps storage under the /apis/registry/v3/admin/gitops path. The endpoints return an HTTP 409 response when a different storage variant is active.
| Path | Method | Description |
|---|---|---|
|
|
Return the synchronization status. |
|
|
Trigger an immediate synchronization. Returns an HTTP |
|
|
Create a dry-run validation task for a Git reference. |
|
|
List validation tasks. |
|
|
Return the status and results of a validation task. |
|
|
Delete a validation task and clean up its files. |
The status response contains the following fields:
| Field | Description |
|---|---|
|
Current state: |
|
Timestamp of the last successful load. |
|
Timestamp of the last poll attempt, successful or not. |
|
Counts of the currently loaded data. |
|
Structured load errors. Each error has a |
|
Map of repository identifiers to the abbreviated commit currently loaded from each repository. |
If a load fails because of a data error, for example malformed metadata, a missing content file, or a rule violation, the registry does not retry the same commit. Push a new commit that fixes the problem to trigger a fresh load. Transient errors are retried on the next poll.
