Streaming & Messaging AWS

Apicurio Registry 3.3.3 on AWS User Guide

| Product: Apicurio Registry 3.3.3 on Ubuntu 24.04 on AWS

Overview

This guide covers launching and using the Apicurio Registry 3.3.3 on Ubuntu 24.04 AMI from cloudimg on Amazon EC2. It is one version of the cloudimg streaming and messaging listing on AWS Marketplace; you pick it as the software version when you subscribe and launch.

Apicurio Registry is an open-source registry for event schemas and API designs: Avro, Protobuf, JSON Schema, OpenAPI, AsyncAPI, GraphQL, WSDL and XSD. Producers and consumers look schemas up at runtime, and compatibility rules stop a breaking change from being registered. It has a web UI, its own REST API (v3, plus the older v2) and a Confluent-compatible Schema Registry API, so Kafka clients that use Confluent's serializers work with it unchanged, as do Apicurio's own Kafka SerDes.

What the cloudimg image adds:

  • HTTPS only, from the first boot. nginx serves the web UI and the APIs on port 443 (port 80 only redirects) with a certificate generated on your instance for its own names and addresses. It is an end-entity certificate: it can only vouch for this instance and can never sign other certificates. Install your own CA-issued certificate with one command (Step 12); the boot-time renewal never replaces it.
  • A login on every page and every API call, enforced twice. Apicurio Registry ships with authentication switched off. On this image every path asks for a login at the front door (nginx), and the registry itself authenticates every API call again with its own user store and applies its role-based authorization: admin (everything, including exports and settings), developer (register and change schemas and rules) and reader (read only). Each user's password is generated on your instance at first boot; failed logins are rate limited. Add, re-key or remove users with sudo apicurio-user (Step 9).
  • No default secret, and a database created on your instance. The image ships no password, key or database. At first boot the instance creates its own PostgreSQL cluster, the registry's database and its database role (the password is generated there and PostgreSQL only ever sees its SCRAM-SHA-256 verifier). The registry and nginx will not start until first boot has finished.
  • Everything else on localhost. The registry (8080), its management endpoint with health and metrics (9000) and PostgreSQL (5432) listen on 127.0.0.1 only, checked every time a service starts.
  • Guarded browser requests. Requests with another site's Origin, writes a browser marks as cross-site, WebSocket upgrades and ; path parameters are refused. Exports and downloads of the registry's data must come from the registry's own page (a cross-site request, a link opened from outside the browser or a request without a browser signal gets a 403), so another web page cannot use your browser's saved login to pull your schemas. CORS is off.
  • Instance metadata blocked. The registry and PostgreSQL may reach nothing but localhost; nginx and the boot service are blocked from the instance metadata service. The AMI requires IMDSv2.
  • Hardened defaults. The registry runs as an unprivileged user under systemd sandboxing, nginx hands it the real client address and the configured host name (never values a client sends), SSH accepts keys only, and Ubuntu 24.04 LTS is fully patched at build time with unattended security upgrades switched on.

What is on the image: Apicurio Registry 3.3.3 (the official release from Maven Central, verified against its published SHA-512 digest and the release signature), the official Apicurio Registry 3.3.3 web UI (taken from the project's release image by digest), the Eclipse Temurin 21.0.13 Java runtime (verified against its published SHA-256), PostgreSQL 16 and nginx 1.24 (from Ubuntu 24.04), cloud-init and the AWS Systems Manager agent.

Default user: ubuntu · Architecture: x86_64 · Recommended instance type: m5.large

The Apicurio Registry 3.3.3 web UI over HTTPS: the Explore page with the default group and the demo group used in this guide

Prerequisites

  • An AWS account subscribed to the listing, an EC2 key pair and a VPC subnet with a route to the internet (or a private route you use for management).
  • A security group that allows TCP 22 (SSH) and TCP 443 (the UI and the APIs) from your admin network and from the Kafka clients that use the registry, plus TCP 80 if you want plain HTTP requests redirected to HTTPS. There is nothing else to open: the registry and PostgreSQL listen on the loopback address only and refuse connections from other machines even if a security group allows them.

Step 1: Launch the instance

In the AWS Marketplace listing choose Continue to Subscribe, then Continue to Configuration, pick Apicurio Registry 3.3.3 on Ubuntu 24.04 as the software version and your Region, then Launch through EC2. Choose m5.large, your key pair and a security group as above.

With the AWS CLI (replace the values in angle brackets):

aws ec2 run-instances --region <region> --image-id <ami-id-from-the-listing> \
  --instance-type m5.large --key-name <your-key-pair> \
  --security-group-ids <your-security-group> --subnet-id <your-subnet> \
  --metadata-options HttpTokens=required,HttpEndpoint=enabled \
  --tag-specifications 'ResourceType=instance,Tags=[{Key=Name,Value=apicurio-registry}]'

First boot takes about a minute after the instance is running: the boot service creates the PostgreSQL cluster, the users and the certificate, then PostgreSQL, the registry (which creates its tables) and nginx start.

Step 2: Connect to your instance

Version SSH user
Apicurio Registry 3.3.3 on Ubuntu 24.04 ubuntu
ssh -i <your-key.pem> ubuntu@<public-ip>

Step 3: Check the services and the listening set

systemctl is-active apicurio-metadata-names apicurio-firstboot postgresql@16-main apicurio-registry nginx
sudo test -f /var/lib/cloudimg/apicurio-firstboot.done && echo FIRSTBOOT_DONE

All five units are active and the first boot marker is present:

active
active
active
active
active
FIRSTBOOT_DONE

Only SSH and nginx listen on network addresses; PostgreSQL (5432), the registry (8080) and its management endpoint (9000) listen on 127.0.0.1:

sudo ss -lntp | awk 'NR>1 {print $4, $6}' | grep -v '127.0.0.5' | sed -E 's/,pid=[0-9]+,fd=[0-9]+//g' | sort
0.0.0.0:22 users:(("sshd"),("systemd"))
0.0.0.0:443 users:(("nginx"),("nginx"),("nginx"))
0.0.0.0:80 users:(("nginx"),("nginx"),("nginx"))
127.0.0.1:5432 users:(("postgres"))
127.0.0.1:8080 users:(("java"))
127.0.0.1:9000 users:(("java"))
[::]:22 users:(("sshd"),("systemd"))
[::]:443 users:(("nginx"),("nginx"),("nginx"))
[::]:80 users:(("nginx"),("nginx"),("nginx"))

Step 4: Get the URLs, the logins and the certificate fingerprint

The credentials file is readable by root only. It holds the URLs, the three logins with their roles, the SHA-256 fingerprint of this instance's certificate and the registry's own database login (which only the registry uses):

sudo cat /root/apicurio-credentials.txt
# Apicurio Registry 3.3.3 on Ubuntu 24.04 - generated on first boot by apicurio-firstboot.service.
# Every password is unique to this instance; none was baked into the image. Keep this file secret.
# The registry and its UI are served over HTTPS only (port 443; port 80 redirects). The certificate is self signed
# (minted on this instance): compare its SHA-256 fingerprint with APICURIO_TLS_CERT_SHA256 below before you sign in,
# or install a CA-issued certificate with: sudo apicurio-install-cert <chain.pem> <key.pem>
# Users and roles: admin = sr-admin, developer = sr-developer (register and change artifacts), reader = sr-readonly.
# Manage users: sudo apicurio-user list|add|passwd|remove     Re-verify the appliance: sudo apicurio-selfcheck
APICURIO_URL=https://<public-ip>/
APICURIO_HOST=<public-ip>
APICURIO_API_URL=https://<public-ip>/apis/registry/v3
APICURIO_CCOMPAT_URL=https://<public-ip>/apis/ccompat/v7
APICURIO_TLS_CERT_SHA256=<sha256-fingerprint>
APICURIO_TLS_CERT_FILE=/etc/cloudimg/apicurio-registry.pem
APICURIO_ADMIN_USER=admin
APICURIO_ADMIN_ROLE=sr-admin
APICURIO_ADMIN_PASSWORD=<APICURIO_ADMIN_PASSWORD>
APICURIO_DEVELOPER_USER=developer
APICURIO_DEVELOPER_ROLE=sr-developer
APICURIO_DEVELOPER_PASSWORD=<APICURIO_DEVELOPER_PASSWORD>
APICURIO_READER_USER=reader
APICURIO_READER_ROLE=sr-readonly
APICURIO_READER_PASSWORD=<APICURIO_READER_PASSWORD>
# PostgreSQL (127.0.0.1:5432; the registry's own login, used by the registry only):
APICURIO_DB_NAME=apicurio
APICURIO_DB_USER=apicurio
APICURIO_DB_PASSWORD=<generated>
# REST API with a login from a curl config (never on the command line):
#   sudo curl --cacert /etc/cloudimg/apicurio-registry.pem -K /root/apicurio-admin.curl https://<public-ip>/apis/registry/v3/system/info
# User guide: https://www.cloudimg.co.uk/guides/apicurio-registry-3-3-3-on-ubuntu-24-04-aws/

The URLs, the users and the fingerprint (without the passwords):

sudo grep -E '^APICURIO_(URL|API_URL|CCOMPAT_URL|TLS_CERT_SHA256|[A-Z]+_USER|[A-Z]+_ROLE)=' /root/apicurio-credentials.txt
APICURIO_URL=https://<public-ip>/
APICURIO_API_URL=https://<public-ip>/apis/registry/v3
APICURIO_CCOMPAT_URL=https://<public-ip>/apis/ccompat/v7
APICURIO_TLS_CERT_SHA256=<sha256-fingerprint>
APICURIO_ADMIN_USER=admin
APICURIO_ADMIN_ROLE=sr-admin
APICURIO_DEVELOPER_USER=developer
APICURIO_DEVELOPER_ROLE=sr-developer
APICURIO_READER_USER=reader
APICURIO_READER_ROLE=sr-readonly
APICURIO_DB_USER=apicurio

The host is the instance's public IP address from the EC2 instance metadata (the private address when the instance has no public one). The certificate also names the public DNS name, the private IP address and the private DNS name. If you stop and start the instance without an Elastic IP, the boot service picks up the new address and issues a new certificate for it (unless you installed your own, Step 12).

The certificate's fingerprint and names, read from the instance:

sudo openssl x509 -in /etc/cloudimg/apicurio-registry.pem -noout -fingerprint -sha256
openssl x509 -in /etc/cloudimg/apicurio-registry.pem -noout -ext subjectAltName,basicConstraints,keyUsage,extendedKeyUsage
sha256 Fingerprint=<sha256-fingerprint>
X509v3 Subject Alternative Name:
    IP Address:<public-ip>, DNS:ec2-<public-ip-dashed>.compute-1.amazonaws.com, IP Address:<private-ip>, DNS:ip-<private-ip-dashed>.ec2.internal, IP Address:127.0.0.1, DNS:localhost
X509v3 Basic Constraints: critical
    CA:FALSE
X509v3 Key Usage: critical
    Digital Signature, Key Encipherment
X509v3 Extended Key Usage:
    TLS Web Server Authentication

Calling the API from the instance itself. The examples below run on the instance as root with a login from a curl config file (-K /root/apicurio-<user>.curl), so no password is ever on a command line, and they pin this instance's certificate (--cacert). They send the request to the registry's own name but connect to 127.0.0.1 (--resolve): a connection from the instance to its own public address leaves through the internet gateway and is then subject to your security group. From your own machine use the URL directly.

Step 5: Open the web UI

Browse to the APICURIO_URL from Step 4. The certificate is self signed and unique to this instance, so the browser warns about it the first time. Check the certificate before you continue: open the browser's certificate viewer for the page and compare its SHA-256 fingerprint with APICURIO_TLS_CERT_SHA256 from the credentials file. Continue only if they match, and only for this site. Never add this certificate to an operating-system or browser trust store; for a certificate browsers trust without a warning, install a CA-issued one (Step 12).

The browser then asks for a user name and password: sign in as admin, developer or reader with the matching password from the credentials file. To print just one password on the instance:

sudo awk -F= '$1=="APICURIO_ADMIN_PASSWORD" {print $2}' /root/apicurio-credentials.txt

The Explore page lists the groups (screenshot at the top of this guide: default, which holds the Confluent-compatible subjects, and the demo group registered in Step 6). Open a group, then an artifact, to see its metadata and versions, with tabs for its rules, branches, contract and usage:

An artifact page in the web UI: the orders-value Avro schema registered in Step 7, owned by developer, with its metadata and its version list

Each version has its own page with its metadata, state and comments, and tabs for the documentation, the content and the references:

A version page in the web UI: demo / user-signup / 1.0.0, a JSON Schema registered in Step 6, with its metadata and the Download button

The roles are enforced by the registry, not by the page: the UI shows every action to every user, and an action the user's role does not allow is refused with an error (for example reader cannot create or change an artifact). Global rules and the registry's settings are under Global rules and Settings (admin):

The Global rules page in the web UI: the Validity rule set to Full, which this guide enables in Step 8

There is no sign-out button: the browser keeps the HTTP login until you close it. Use a private window for a short session, or close the browser when you are done.

Step 6: Register and fetch a schema with the REST API (v3)

The core REST API is at /apis/registry/v3; its reference documentation is served by the registry at https://<public-ip>/apis/registry/v3 (behind the same login). Register a JSON Schema as developer (the FIND_OR_CREATE_VERSION option makes the call safe to repeat):

H=$(sudo sed -n 's/^APICURIO_HOST=//p' /root/apicurio-credentials.txt)
sudo curl -s --cacert /etc/cloudimg/apicurio-registry.pem --resolve "${H}:443:127.0.0.1" -K /root/apicurio-developer.curl \
  -H 'Content-Type: application/json' --data @- \
  "https://${H}/apis/registry/v3/groups/demo/artifacts?ifExists=FIND_OR_CREATE_VERSION" <<'JSON' | python3 -m json.tool
{"artifactId": "user-signup", "artifactType": "JSON", "name": "User signup event",
 "firstVersion": {"version": "1.0.0", "content": {"contentType": "application/json",
   "content": "{\"$schema\":\"https://json-schema.org/draft/2020-12/schema\",\"title\":\"UserSignup\",\"type\":\"object\",\"properties\":{\"userId\":{\"type\":\"string\"},\"email\":{\"type\":\"string\"}},\"required\":[\"userId\"]}"}}}
JSON

The registry answers with the artifact and its first version (your ids and times differ):

{
    "artifact": {
        "owner": "developer",
        "createdOn": "2026-10-29T08:51:12Z",
        "modifiedBy": "developer",
        "modifiedOn": "2026-10-29T08:51:12Z",
        "artifactType": "JSON",
        "groupId": "demo",
        "artifactId": "user-signup",
        "name": "User signup event"
    },
    "version": {
        "version": "1.0.0",
        "owner": "developer",
        "createdOn": "2026-10-29T08:51:12Z",
        "artifactType": "JSON",
        "globalId": 1,
        "state": "ENABLED",
        "groupId": "demo",
        "contentId": 1,
        "artifactId": "user-signup"
    }
}

Fetch the latest content, and list the group, as reader:

H=$(sudo sed -n 's/^APICURIO_HOST=//p' /root/apicurio-credentials.txt)
R=(--cacert /etc/cloudimg/apicurio-registry.pem --resolve "${H}:443:127.0.0.1" -K /root/apicurio-reader.curl)
sudo curl -s "${R[@]}" "https://${H}/apis/registry/v3/groups/demo/artifacts/user-signup/versions/branch=latest/content"; echo
sudo curl -s "${R[@]}" "https://${H}/apis/registry/v3/groups/demo/artifacts" | python3 -c 'import json,sys; [print(a["artifactId"], a["artifactType"]) for a in json.load(sys.stdin)["artifacts"]]'
{"$schema":"https://json-schema.org/draft/2020-12/schema","title":"UserSignup","type":"object","properties":{"userId":{"type":"string"},"email":{"type":"string"}},"required":["userId"]}
user-signup JSON

reader cannot change anything; the registry refuses the write with a 403:

H=$(sudo sed -n 's/^APICURIO_HOST=//p' /root/apicurio-credentials.txt)
sudo curl -s -o /dev/null -w '%{http_code}\n' --cacert /etc/cloudimg/apicurio-registry.pem --resolve "${H}:443:127.0.0.1" -K /root/apicurio-reader.curl \
  -H 'Content-Type: application/json' --data '{"artifactId":"not-allowed","artifactType":"JSON","firstVersion":{"content":{"contentType":"application/json","content":"{}"}}}' \
  "https://${H}/apis/registry/v3/groups/demo/artifacts"
403

Step 7: Use the Confluent-compatible Schema Registry API

The Confluent-compatible API is at /apis/ccompat/v7: this is the URL Kafka clients built for Confluent Schema Registry use (Step 10). A subject is an artifact in the default group. Register an Avro schema for the orders topic's values as developer, set the subject's compatibility to BACKWARD, then check two changes against it as reader:

H=$(sudo sed -n 's/^APICURIO_HOST=//p' /root/apicurio-credentials.txt)
D=(--cacert /etc/cloudimg/apicurio-registry.pem --resolve "${H}:443:127.0.0.1" -K /root/apicurio-developer.curl -H 'Content-Type: application/vnd.schemaregistry.v1+json')
R=(--cacert /etc/cloudimg/apicurio-registry.pem --resolve "${H}:443:127.0.0.1" -K /root/apicurio-reader.curl -H 'Content-Type: application/vnd.schemaregistry.v1+json')
C="https://${H}/apis/ccompat/v7"
sudo curl -s "${D[@]}" --data '{"schema":"{\"type\":\"record\",\"name\":\"Order\",\"namespace\":\"demo\",\"fields\":[{\"name\":\"orderId\",\"type\":\"string\"},{\"name\":\"total\",\"type\":\"double\"}]}"}' "${C}/subjects/orders-value/versions"; echo
sudo curl -s "${D[@]}" -X PUT --data '{"compatibility":"BACKWARD"}' "${C}/config/orders-value"; echo
# adds an optional field (a default value): backward compatible
sudo curl -s "${R[@]}" --data '{"schema":"{\"type\":\"record\",\"name\":\"Order\",\"namespace\":\"demo\",\"fields\":[{\"name\":\"orderId\",\"type\":\"string\"},{\"name\":\"total\",\"type\":\"double\"},{\"name\":\"currency\",\"type\":\"string\",\"default\":\"EUR\"}]}"}' "${C}/compatibility/subjects/orders-value/versions/latest"; echo
# changes a field's type: not compatible
sudo curl -s "${R[@]}" --data '{"schema":"{\"type\":\"record\",\"name\":\"Order\",\"namespace\":\"demo\",\"fields\":[{\"name\":\"orderId\",\"type\":\"long\"},{\"name\":\"total\",\"type\":\"double\"}]}"}' "${C}/compatibility/subjects/orders-value/versions/latest"; echo
sudo curl -s "${R[@]}" "${C}/subjects"; echo
{"id":2}
{"compatibility":"BACKWARD","normalize":false}
{"is_compatible":true}
{"is_compatible":false}
["orders-value"]

With the rule in place the registry also refuses to register an incompatible version (HTTP 409), so a producer cannot publish a schema that would break its consumers.

Step 8: Artifact rules

Rules are checked whenever new content is added: Validity (the content parses as its type), Compatibility (against earlier versions: BACKWARD, FORWARD, FULL and their transitive forms) and Integrity (references resolve). They can be set globally (admin), per group or per artifact. Enable a global Validity rule as admin and list the global rules:

H=$(sudo sed -n 's/^APICURIO_HOST=//p' /root/apicurio-credentials.txt)
A=(--cacert /etc/cloudimg/apicurio-registry.pem --resolve "${H}:443:127.0.0.1" -K /root/apicurio-admin.curl)
sudo curl -s -o /dev/null -w '%{http_code}\n' "${A[@]}" -H 'Content-Type: application/json' --data '{"ruleType":"VALIDITY","config":"FULL"}' "https://${H}/apis/registry/v3/admin/rules"
sudo curl -s "${A[@]}" "https://${H}/apis/registry/v3/admin/rules"; echo
204
["VALIDITY"]

(A second run answers 409 for the first call: the rule already exists.) An artifact rule is set the same way under /apis/registry/v3/groups/<group>/artifacts/<artifact>/rules, or in the artifact's Rules panel in the UI.

Step 9: Users and roles

User Role Can
admin sr-admin everything: artifacts, global rules, settings, role management, export and import
developer sr-developer read, register and change artifacts, versions, rules and metadata
reader sr-readonly read only

Give each application its own least-privilege login: a consumer that only looks schemas up needs readonly; a producer that registers schemas, or your CI pipeline, needs developer. Keep admin for administration.

sudo apicurio-user list
admin        sr-admin
developer    sr-developer
reader       sr-readonly

Add a user (here a read-only login for a consumer application), re-key a password, or remove a user. apicurio-user generates the password on the instance, stores it in /root/apicurio-credentials.txt and in a curl config /root/apicurio-<name>.curl, updates both the front door and the registry's user store and restarts the registry (a few seconds):

sudo apicurio-user add orders-consumer readonly
sudo apicurio-user passwd orders-consumer
sudo apicurio-user remove orders-consumer

The last admin cannot be removed. Passwords are never taken on the command line: read the new one from the credentials file (APICURIO_ORDERS_CONSUMER_PASSWORD).

Step 10: Connect Kafka clients

Kafka producers and consumers talk to the registry over HTTPS with one of the logins and this instance's certificate pinned in the client's own trust file (never an operating-system or Java-wide trust store). Copy the certificate to the client machine and check its fingerprint against APICURIO_TLS_CERT_SHA256 before you use it:

scp -i <your-key.pem> ubuntu@<public-ip>:/etc/cloudimg/apicurio-registry.pem ./apicurio-registry.pem
openssl x509 -in ./apicurio-registry.pem -noout -fingerprint -sha256

Apicurio Registry SerDes (io.apicurio:apicurio-registry-avro-serde-kafka and the Protobuf and JSON Schema equivalents) read the certificate file directly:

apicurio.registry.url=https://<public-ip>/apis/registry/v3
apicurio.registry.auth.username=developer
apicurio.registry.auth.password=<APICURIO_DEVELOPER_PASSWORD>
apicurio.registry.tls.truststore.type=PEM
apicurio.registry.tls.truststore.location=/path/to/apicurio-registry.pem
# producers: register new schemas automatically (needs the developer role)
apicurio.registry.auto-register=true

Confluent serializers (io.confluent:kafka-avro-serializer and friends) use the Confluent-compatible API. Make a client-own PKCS12 trust file that holds only this certificate (choose your own trust-file password):

keytool -importcert -noprompt -alias apicurio-registry -file ./apicurio-registry.pem \
  -keystore ./apicurio-client-truststore.p12 -storetype PKCS12 -storepass '<choose-a-password>'
schema.registry.url=https://<public-ip>/apis/ccompat/v7
basic.auth.credentials.source=USER_INFO
basic.auth.user.info=reader:<APICURIO_READER_PASSWORD>
schema.registry.ssl.truststore.location=/path/to/apicurio-client-truststore.p12
schema.registry.ssl.truststore.password=<choose-a-password>
schema.registry.ssl.truststore.type=PKCS12

Keep these files readable by the application only: they hold a login. Use reader for consumers and developer for producers that register schemas; to use the instance's DNS name instead of its IP address, see Step 12. Kafka itself is a separate service (for example the Apache Kafka versions in this listing); the registry only stores and serves the schemas.

Step 11: Export and import, and the browser guard

An export downloads everything in the registry (artifacts, versions, content, rules and settings) as a ZIP file; an import loads such a file into an empty registry. Both are admin operations. In the UI they are under Settings. From a client, an export must carry the same-origin signal a browser sends from the registry's own page (Sec-Fetch-Site: same-origin); without it the request is refused, so a link on another web site cannot make your browser download your registry:

H=$(sudo sed -n 's/^APICURIO_HOST=//p' /root/apicurio-credentials.txt)
A=(--cacert /etc/cloudimg/apicurio-registry.pem --resolve "${H}:443:127.0.0.1" -K /root/apicurio-admin.curl)
sudo curl -s -o /dev/null -w 'no browser signal: %{http_code}\n' "${A[@]}" "https://${H}/apis/registry/v3/admin/export"
sudo curl -s -o /dev/null -w 'cross-site: %{http_code}\n' "${A[@]}" -H 'Sec-Fetch-Site: cross-site' "https://${H}/apis/registry/v3/admin/export"
sudo curl -s -o /tmp/apicurio-export.zip -w 'same-origin: %{http_code}\n' "${A[@]}" -H 'Sec-Fetch-Site: same-origin' "https://${H}/apis/registry/v3/admin/export"
python3 -c 'import sys,zipfile; z=zipfile.ZipFile(sys.argv[1]); print(len(z.namelist()), "entries in the export")' /tmp/apicurio-export.zip
no browser signal: 403
cross-site: 403
same-origin: 200
14 entries in the export

The same guard covers the one-time download links the UI uses for exports and the per-version and contract exports. Import a ZIP into a new, empty instance with POST /apis/registry/v3/admin/import (Content-Type: application/zip, admin login).

Step 12: Your own host name and certificate

To serve the registry under a DNS name you own (point an A record at the instance or its Elastic IP first):

sudo apicurio-set-hostname registry.example.com

This issues a new self-signed certificate for the name (plus the instance's own names and addresses), pins nginx and the browser guards to it and updates the URLs and the fingerprint in the credentials file. To use a certificate from your certificate authority (or Let's Encrypt) instead, copy the certificate chain (server certificate first) and its unencrypted private key to the instance and install them:

sudo apicurio-install-cert /path/to/fullchain.pem /path/to/privkey.pem registry.example.com

The boot-time renewal never replaces an installed certificate; sudo apicurio-set-hostname <name> goes back to a self-signed one. With a CA-issued certificate, Kafka clients can trust it the normal way and the client-own trust file from Step 10 is no longer needed.

Step 13: Back up the registry's data

The registry's data lives in its PostgreSQL database apicurio. Back it up with pg_dump (as the postgres administrator, over the local socket):

sudo -u postgres pg_dump -Fc apicurio > /var/backups/apicurio-$(date +%F).dump

To restore into this or a new instance of the same version, stop the registry, recreate the database empty and restore the dump, then start the registry:

sudo systemctl stop apicurio-registry
sudo -u postgres psql -c 'DROP DATABASE apicurio' -c 'CREATE DATABASE apicurio OWNER apicurio'
sudo -u postgres pg_restore -d apicurio --no-owner --role=apicurio /var/backups/apicurio-<date>.dump
sudo systemctl start apicurio-registry

An export ZIP (Step 11) is a second, version-independent backup. Copy backups off the instance (for example to Amazon S3), and keep /root/apicurio-credentials.txt with them. Taking EBS snapshots of the volume also works.

Step 14: Instance metadata and outbound connections

The registry and PostgreSQL may reach nothing but localhost; nginx and the boot service may reach anything except the instance metadata service:

for u in apicurio-registry postgresql@16-main nginx apicurio-firstboot; do printf '%s: ' "$u"; systemctl show -p IPAddressDeny --value "$u"; done
apicurio-registry: 0.0.0.0/0 ::/0
postgresql@16-main: 0.0.0.0/0 ::/0
nginx: 169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 ::ffff:169.254.169.254/128 ::ffff:168.63.129.16/128
apicurio-firstboot: 169.254.169.254/32 168.63.129.16/32 fd00:ec2::254/128 ::ffff:169.254.169.254/128 ::ffff:168.63.129.16/128

(The registry and PostgreSQL units add IPAddressAllow=localhost.) Nothing in the registry's default configuration connects out. If you switch on a feature that does, such as publishing registry events to Kafka, allow just that destination with a drop-in and keep the metadata address denied, for example sudo systemctl edit apicurio-registry with:

[Service]
IPAddressAllow=10.0.12.0/24

Step 15: Service management, logs and health

systemctl show apicurio-registry -p ActiveState -p SubState -p User --no-pager
sudo journalctl -u apicurio-registry -b --no-pager -o cat --grep 'started in' -n 1
User=apicurio
ActiveState=active
SubState=running
2026-10+00-29 08:43:26 INFO [io.quarkus.bootstrap.runner.Timing] (main) apicurio-registry-app 3.3.3 on JVM (powered by Quarkus 3.33.3.1) started in 13.300s. Listening on: http://127.0.0.1:8080. Management interface listening on http://127.0.0.1:9000.

The registry logs to the journal (journalctl -u apicurio-registry), including an access log line per request and an audit line per change (apicurio.audit); nginx logs to /var/log/nginx/, PostgreSQL to /var/log/postgresql/. The registry's configuration is in /etc/apicurio-registry/registry.properties (no secrets; the per-instance passwords are in secrets.properties next to it). After a change: sudo systemctl restart apicurio-registry.

/healthz answers without a login, with the body ok, only while the registry reports ready (for load balancer health checks):

H=$(sudo sed -n 's/^APICURIO_HOST=//p' /root/apicurio-credentials.txt)
curl -s --cacert /etc/cloudimg/apicurio-registry.pem --resolve "${H}:443:127.0.0.1" "https://${H}/healthz"
ok

The self-check re-verifies the whole appliance with this instance's own logins: services, the listening set, the login wall and the roles, a schema round trip with a compatibility check, the browser guards, the metadata block and the certificate:

sudo apicurio-selfcheck | tail -1
SELFCHECK OK - Apicurio Registry 3.3.3 on loopback behind HTTPS, per-instance logins at the front door and in the registry (roles enforced), origin and cross-site guards, exact listening set, metadata block verified.

Support

cloudimg provides support for this image by email at support@cloudimg.co.uk. Include the instance ID, the region and the output of sudo apicurio-selfcheck. Apicurio Registry documentation: https://www.apicur.io/registry/docs/.

Apicurio is a trademark of Red Hat, Inc. cloudimg is not affiliated with or endorsed by Red Hat or the Apicurio project. Kafka is a trademark of the Apache Software Foundation. Confluent is a trademark of Confluent, Inc.