Run a Marketplace on Kubernetes
In this tutorial you deploy a Marketplace on a Kubernetes cluster with the repository's Helm chart, let it index one public registry, open the web UI in your browser, and install a resource from it with the agent-marketplace CLI.
The deployment runs one all-in-one Marketplace pod with SQLite on a persistent volume, plus the Nuxt web UI, both behind one ingress. At the end you delete the namespace and the throwaway checkout. Deploy a production Marketplace shows the split mode with PostgreSQL and S3 that you grow into when you need more than one replica.
No authentication on the public routes
The Marketplace has no built-in authentication, authorization, or TLS. This tutorial uses a placeholder hostname that resolves only on your machine, but anyone who can reach your cluster's ingress and guesses that host header can still browse the deployment. Run the tutorial on a cluster you control, and put an authenticating TLS boundary in front of the public routes before the Marketplace matters; see How to configure a reverse proxy.
Before you begin
You need:
kubectlconnected to a Kubernetes cluster you control, and Helm 3. A local kind cluster works if you created it with the ingress port mappings that publish port 80 on127.0.0.1.- Cluster nodes that run
linux/amd64images. The release images are built forlinux/amd64only; a kind cluster on an Apple silicon Mac hasarm64nodes. - An ingress controller in the cluster. The examples use the class
nginx; changeingress.classNameif yours differs. - The
agent-marketplaceCLI on yourPATH(see Install). - The latest release tag from the releases page. Every release publishes the Marketplace
workerandapiimages and thewebimage to GHCR; releases that predate image publishing have none.
Run all steps in the same terminal window so the variables stay set.
1. Check out the chart at your version
Set the Marketplace version and clone the repository at that tag. The clone provides the Helm chart at the same version as your images:
export MARKETPLACE_VERSION=v4.2.0 # replace with the latest release tag
TUTORIAL_ROOT=$(mktemp -d)
git clone --depth 1 --branch "$MARKETPLACE_VERSION" \
https://github.com/Adyen/agent-marketplace.git "$TUTORIAL_ROOT/agent-marketplace"
cd "$TUTORIAL_ROOT/agent-marketplace"2. Create the namespace
Keep the tutorial in its own namespace so the cleanup in step 11 removes everything at once:
kubectl create namespace marketplace-tutorial3. Write the values file
The chart's defaults already run the all-in-one Marketplace with SQLite and a persistent volume claim. Create values.yaml in the checkout root that pins the images, enables the web UI, routes one hostname through the ingress, and adds the operator configuration with one registry:
cat > values.yaml <<EOF
image:
repository: ghcr.io/adyen/agent-marketplace/worker
tag: ${MARKETPLACE_VERSION}
web:
enabled: true
image:
repository: ghcr.io/adyen/agent-marketplace/web
tag: ${MARKETPLACE_VERSION}
persistence:
enabled: true
config:
content: |
apiVersion: agent-marketplace.io/marketplace/v1
instance:
id: kubernetes-tutorial
name: Kubernetes Tutorial Marketplace
canonical_url: http://marketplace.example.com
browser_base: http://marketplace.example.com
listen: 0.0.0.0:8080
operations_listen: 0.0.0.0:9090
storage:
metadata:
driver: sqlite
dsn: /var/lib/agent-marketplace/marketplace.db
blobs:
driver: filesystem
path: /var/lib/agent-marketplace/blobs
acquisition_default: mirror
refresh:
interval: 30m
# SQLite is single-process in this example; keep refresh concurrency low.
refresh_concurrency: 1
policy:
license_undeclared: allow
registries:
- id: skills
slug: skills
origin: https://github.com/anthropics/skills.git
ref: main
roots:
- path: skills
ingress:
enabled: true
className: nginx
hosts:
- host: marketplace.example.com
paths:
- path: /.well-known/agent-marketplace.json
pathType: Exact
service: api
port: 8080
- path: /api/
pathType: Prefix
service: api
port: 8080
- path: /healthz
pathType: Exact
service: api
port: 8080
- path: /
pathType: Prefix
service: web
port: 3000
EOFFour things in this file deserve a second look:
- The Marketplace image is the
workertarget. The all-in-one role runs acquisition, which needs Git, and only the worker image contains it. marketplace.example.comis a placeholder host. With the/etc/hostsentry from step 7, only your machine resolves it.- The
registriesentry tells the Marketplace to clonehttps://github.com/anthropics/skills.gitatmainand scan itsskillsdirectory.license_undeclared: allowaccepts resources that declare no license; without it, the Marketplace rejects them. - The ingress routes the well-known document,
/api/, and/healthzto the API, and/to the web UI at the origin root. The web UI calls the API at the same origin, which is why both live behind one host.
Private GHCR packages
If your organization keeps its GHCR packages private, set GHCR_USERNAME to your GitHub username and GHCR_TOKEN to a token with the read:packages scope. Then create a pull secret and add it to values.yaml under a top-level imagePullSecrets key:
kubectl create secret docker-registry ghcr-pull \
--namespace marketplace-tutorial \
--docker-server=ghcr.io \
--docker-username="$GHCR_USERNAME" \
--docker-password="$GHCR_TOKEN"imagePullSecrets:
- name: ghcr-pull4. Render and review
Lint the chart with your values and render the full manifest before applying it:
helm lint --strict deploy/helm/agent-marketplace -f values.yaml
helm template marketplace deploy/helm/agent-marketplace \
--namespace marketplace-tutorial \
-f values.yaml > rendered.yamlhelm lint passes with 1 chart(s) linted, 0 chart(s) failed, and rendered.yaml holds eight objects: two Deployments (marketplace-agent-marketplace-all and marketplace-agent-marketplace-web), two Services, one Ingress, one ConfigMap, one ServiceAccount, and one PersistentVolumeClaim. Confirm the count:
grep -c '^kind:' rendered.yamlExpected output:
85. Install the release
helm upgrade --install marketplace deploy/helm/agent-marketplace \
--namespace marketplace-tutorial \
-f values.yamlHelm reports STATUS: deployed for the marketplace release in the marketplace-tutorial namespace.
6. Watch the rollout
kubectl get deployments,pods,pvc -n marketplace-tutorial -wWait until marketplace-agent-marketplace-all and marketplace-agent-marketplace-web show 1/1 ready and the persistent volume claim shows Bound, then stop the command with Ctrl-C.
Readiness is not just the process starting: the chart probes /readyz on the private operations port (9090) for readiness and startup, and /healthz on the application port for liveness. /readyz returns ready only when the database and blob stores answer.
7. Point your machine at the ingress
kubectl get ingress -n marketplace-tutorialCopy the ADDRESS of the marketplace-agent-marketplace ingress and map the tutorial host to it. On a local kind cluster the address is 127.0.0.1, because the ingress is published on localhost:
echo "<address> marketplace.example.com" | sudo tee -a /etc/hosts8. Verify the Marketplace
Check the public health endpoint and the well-known document:
curl -fsS http://marketplace.example.com/healthz
curl -fsS http://marketplace.example.com/.well-known/agent-marketplace.jsonExpected output (the version field reflects your release):
{"status":"ok"}
{"instance_id":"kubernetes-tutorial","version":"v4.2.0","canonical_url":"http://marketplace.example.com","api_base":"/api/v1","browser_base":"http://marketplace.example.com","browser_latest_route_template":"http://marketplace.example.com/r/{registry}/{kind}/{name}","browser_exact_route_template":"http://marketplace.example.com/r/{registry}/{kind}/{name}/revisions/{revision}","api_versions":["v1"],"artifact_schema_versions":["v1"],"resource_kinds":["skill","command","subagent","hook","mcpserver","collection"],"acquisition_modes":["index-only","mirror"],"features":["inspection"]}The all-in-one pod started indexing the skills registry when it became ready. Poll the search endpoint until the frontend-design skill is published:
until curl -fsS "http://marketplace.example.com/api/v1/search?q=frontend-design" \
| grep -q frontend-design; do sleep 10; done
curl -fsS "http://marketplace.example.com/api/v1/search?q=frontend-design"Expected output (trimmed; the revision digests and score are omitted):
{"items":[{"registry_id":"skills","registry_slug":"skills","kind":"skill","name":"frontend-design","description":"Guidance for distinctive, intentional visual design when building new UI or reshaping an existing one. Helps with aesthetic direction, typography, and making choices that don't read as templated defaults.","availability":"available","metadata":{"license":"Apache-2.0"}}],"limit":100,"offset":0,"total":1}If the loop keeps running for several minutes, check the pod logs for acquisition or indexing errors:
kubectl logs -n marketplace-tutorial deployment/marketplace-agent-marketplace-all9. Open the web UI
Open http://marketplace.example.com/ in your browser. The catalog lists every published skill from the skills registry, each with its name, description, license, and a Copy install command button.
10. Install a resource with the CLI
The consumer side of the tutorial also runs inside the throwaway clone. Replace the clone's .agents/config.yaml with one that selects claude as the target harness, so installed skills are written to .claude/skills/:
cat > .agents/config.yaml <<'EOF'
apiVersion: agent-marketplace/v4
harnesses:
- claude
EOFRegister the tutorial Marketplace in the clone's repository configuration, then install the skill by its full resource URL:
agent-marketplace install http://marketplace.example.com --scope repo
agent-marketplace install http://marketplace.example.com/r/skills/skill/frontend-designThe second command fetches the latest usable revision of the skill, prints a verified preview, and stops before installing, because the preview reports a security finding:
Warning: 1 security finding
WARN [frontend-design] remote reference: http://
Active capabilities: skill
Nothing has been installed.
Error: marketplace install requires risk acceptance: 1 security finding(s); re-run with --accept-risk (or --yes)The skill's instructions reference remote http:// addresses, and the CLI never installs a resource with findings until you have read them and accepted the risk. You can inspect the skill's contents in the web UI from step 9. When you have read the preview, confirm the installation:
agent-marketplace install http://marketplace.example.com/r/skills/skill/frontend-design --accept-riskExpected output:
Installing frontend-design · skill
Source: skills
✓ Installed frontend-designThe repository configuration now records the Marketplace endpoint and pins the exact revision of the skill by its digest. Check the result:
agent-marketplace status
agent-marketplace sync --check
ls .claude/skills/frontend-design/SKILL.mdstatus lists the http://marketplace.example.com endpoint and the installed skill, sync --check exits with status 0 because the files on disk match the configuration, and ls prints the path of the skill's SKILL.md.
11. Clean up
Uninstall the release, delete the namespace, leave the checkout, and remove the temporary directory:
helm uninstall marketplace --namespace marketplace-tutorial
kubectl delete namespace marketplace-tutorial
cd -
rm -rf "$TUTORIAL_ROOT"Then remove the marketplace.example.com line you added to /etc/hosts.
Deleting the namespace deletes the persistent volume claim. Whether the persistent volume and its data go with it depends on the reclaim policy of the cluster's default StorageClass: kind's default deletes them, but a Retain policy keeps them. Run kubectl get pv to check for a Released volume, and remove it and its backing storage yourself if you need to.
What happened
The Helm chart rendered a Marketplace deployment you could grow into a production one:
- One all-in-one Deployment runs the API and the refresh worker in a single process, with SQLite and mirrored blobs on the persistent volume claim.
- One web Deployment serves the Nuxt catalog UI at the origin root and reaches the API through the cluster-internal Service.
- The API Service exposes the public application port 8080 and a private operations port 9090. The Ingress routes only the intended public paths; readiness and metrics stay on the operations port, off the Ingress.
- The ConfigMap holds the operator configuration. When you change
config.content, the chart changes achecksum/configannotation and Kubernetes restarts the Marketplace pods with the new configuration. - The all-in-one pod indexed the
skillsregistry when it started and refreshes it every 30 minutes.
Next steps
- Deploy a production Marketplace — split API and worker Deployments, PostgreSQL and S3, rolling updates, and the trust boundary.
- How to configure a reverse proxy — the public path list, TLS, and authentication for a real boundary.
- How to reindex and monitor a Marketplace — what
/readyzand/metricsreport before you alert on them. - How to enable and use the admin panel — manage registries from the browser with single sign-on.
- The Helm chart README — every chart value, including split mode, existing ConfigMaps, ServiceMonitor, and NetworkPolicy.