Artifact Hub
Overview
Artifact Hub is a web-based catalogue for finding, installing and publishing cloud-native packages. It indexes Helm charts, OLM operators, OPA and Kyverno policies, Falco rules, Tekton tasks and pipelines, Krew kubectl plugins, container images and around thirty other artifact kinds. It presents each one with versions, changelogs, values, security context and rendered documentation. Artifact Hub is a Cloud Native Computing Foundation incubating project.
A self-hosted Artifact Hub is a private catalogue of your own charts and operators. Your platform team publishes to it, your developers search it, and your organisations and accounts control who sees what. Nothing depends on a third-party service being reachable.
This SecureAnyCloud image runs Artifact Hub 1.23.0, compiled from the pinned upstream release, alongside PostgreSQL 16 on the same VM. It uses a hardened Ubuntu 24.04 LTS base, patched at build time. The database is a same-box dependency, so the VM boots and serves on its own with nothing else to stand up.
Upstream's sample data seeds an account of demo@artifacthub.io / changeme. This image never creates it. The database is migrated with sample data switched off, so that account has never existed on your VM, and the image ships with zero accounts. On the first boot of every VM, a one-shot service generates the database password, session cookie key, CSRF key and TLS certificate for that VM. It then creates a single administrator with a random password and proves that the administrator can sign in. Nothing is bound to a network port until that finishes, and no secret is shared between customers.
What you get:
- Artifact Hub 1.23.0 (Apache 2.0 licensed), built from the pinned upstream release and managed by systemd
- The web interface and REST API on
:443over HTTPS behind nginx, with:80redirecting to it - The repository tracker as a systemd timer that re-indexes every registered repository every 30 minutes
- The bundled documentation at
/docsand the OpenAPI reference at/docs/api, served by your own VM - The
ahcommand-line linter for validating repository metadata before you publish - PostgreSQL 16 on the same VM, reachable only over loopback
- A per-VM database password, session cookie key, CSRF key and administrator password, generated on first boot and recorded in a root-only file
- Zero accounts in the shipped image, so the upstream demo credential can never be used against your VM
- A TLS certificate generated on first boot for the VM's own addresses, so no private key ships in the image
- A starter public repository registered under your administrator account, so the catalogue is populated on day one and removable in one click. It is registered with vulnerability scanning switched off, because the optional Trivy scanner is not part of this image
- A default-drop
nftablesfirewall, so only22,80and443are reachable from the network postgresql.service,artifacthub-hub.service,nginx.service,nftables.serviceandartifacthub-tracker.timeras enabled units, so the stack returns after a reboot- 24/7 SecureAnyCloud support
Prerequisites
You need an active Azure subscription, an SSH key pair, and a VNet with a subnet in the target region.
Standard_B2ms (2 vCPU / 8 GiB RAM) is the recommended starting size. The VM runs PostgreSQL, the web server and a tracker that parses every chart in every registered repository on a schedule. Choose a larger size if you register many large repositories.
Configure the network security group (NSG) to allow inbound 22/tcp from your management network and 443/tcp so your browser can reach the catalogue. Also allow 80/tcp if you want the automatic redirect from http:// to work. The VM needs outbound HTTPS to reach whichever repositories you register.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Artifact Hub by SecureAnyCloud, and select Create.
On the Basics tab, pick your subscription, resource group, region and size. Under Administrator account, choose SSH public key and paste your key. Under Inbound port rules, allow SSH (22), HTTPS (443) and HTTP (80).
Select Review + create, then Create.
Step 2 - Deploy from the Azure CLI
# Accept the Marketplace terms once per subscription (required before the first CLI deploy)
az vm image terms accept --urn sac1647283583153:artifact-hub:default:latest
az vm create \
--resource-group <your-rg> \
--name artifact-hub \
--image sac1647283583153:artifact-hub:default:latest \
--size Standard_B2ms \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku StandardOpen the web ports so your browser can reach the catalogue:
az vm open-port --resource-group <your-rg> --name artifact-hub --port 443 --priority 1001
az vm open-port --resource-group <your-rg> --name artifact-hub --port 80 --priority 1002First-boot provisioning takes about 30 seconds after the VM reports Running. SSH in with the administrator username you chose:
ssh azureuser@<public-ip>Step 3 - Confirm the services are running
Four units make up the appliance, plus the timer that keeps the catalogue fresh. All of them are enabled, so the stack comes back on its own after a reboot.
for u in postgresql.service artifacthub-hub.service nginx.service nftables.service; do printf '%-28s %s / %s\n' "$u" "$(systemctl is-active "$u")" "$(systemctl is-enabled "$u")" done printf '%-28s %s / %s\n' artifacthub-tracker.timer \
"$(systemctl is-active artifacthub-tracker.timer)" "$(systemctl is-enabled artifacthub-tracker.timer)"Expected output:
postgresql.service active / enabled artifacthub-hub.service active / enabled nginx.service active / enabled nftables.service active / enabled artifacthub-tracker.timer active / enabledConfirm that the web interface answers and that plain HTTP redirects to it. The certificate is the one your VM generated on first boot, so curl accepts it with -k. Your browser shows a warning until you install your own certificate in Step 12.
printf 'HTTPS : HTTP %s\n' "$(curl -ks -o /dev/null -w '%{http_code}' https://127.0.0.1/)" printf 'HTTP 80: HTTP %s\n' "$(curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1/)" curl -ks https://127.0.0.1/ | grep -o 'artifacthub:siteName" content="[^"]*"'Expected output:
HTTPS : HTTP 200 HTTP 80: HTTP 301 artifacthub:siteName" content="Artifact hub"The API is served by this VM on the same origin as the web interface. Request the catalogue statistics:
curl -ks https://127.0.0.1/api/v1/packages/statsExpected output. One starter repository is registered out of the box, so the catalogue is already populated. Your figures grow as you add your own repositories:
{"packages" : 1, "releases" : 44}Step 4 - Confirm no default credential shipped
Upstream's sample data seeds demo@artifacthub.io with the password changeme. This image is built with sample data switched off, so the account was never created. Check both halves: the database has no such row, and the running server rejects the credential.
echo "accounts on this VM : $(sudo -u postgres psql -tAc 'SELECT count(*) FROM "user"' -d hub | tr -d ' ')"
echo "upstream demo account : $(sudo -u postgres psql -tAc "SELECT count(*) FROM \"user\" WHERE email='demo@artifacthub.io'" -d hub | tr -d ' ') row(s)"
JAR=$(mktemp)
TOK=$(curl -ks -c "$JAR" -D - -o /dev/null https://127.0.0.1/api/v1/csrf | tr -d '\r' | awk 'tolower($1) ~ /^x-csrf-token:/ {print $2}')
CODE=$(printf '{"email":"demo@artifacthub.io","password":"changeme"}' \
| curl -ks -b "$JAR" -H "X-CSRF-Token: $TOK" -H 'Content-Type: application/json' \
-H 'Referer: https://127.0.0.1/' --data @- \
-o /dev/null -w '%{http_code}' https://127.0.0.1/api/v1/users/login) rm -f "$JAR"
echo "demo credential sign in : HTTP $CODE" test "$CODE" != "200" || {
echo "UNEXPECTED: the demo credential was accepted"; exit 1; }Expected output:
accounts on this VM : 1 upstream demo account : 0 row(s) demo credential sign in : HTTP 401The single account is the administrator that your VM generated in Step 6.
Step 5 - Review the host firewall
The image ships a default-drop nftables ruleset. PostgreSQL and the server's Prometheus metrics endpoint are bound to loopback only, so widening your NSG later cannot expose them.
sudo nft list ruleset | grep -E 'policy drop|dport' echo "--- listening sockets ---" sudo ss -tlnp | awk '{print $1, $4}' | grep -E ':(22|80|443|5432|8000|8001)$'Expected output:
type filter hook input priority filter; policy drop; tcp dport { 22, 80, 443 } accept type filter hook forward priority filter; policy drop; ip daddr 168.63.129.16 tcp dport 53 counter packets 0 bytes 0 accept --- listening sockets --- LISTEN 0.0.0.0:80 LISTEN 0.0.0.0:22 LISTEN 0.0.0.0:443 LISTEN 127.0.0.1:5432 LISTEN 127.0.0.1:8000 LISTEN 127.0.0.1:8001 LISTEN [::]:80 LISTEN [::]:22 LISTEN [::]:443Only 22, 80 and 443 accept traffic. The last rule in the input chain is Azure's platform DNS resolver, added by the agent. PostgreSQL on 5432, the application server on 8000 and its unauthenticated Prometheus endpoint on 8001 are all bound to 127.0.0.1. They are unreachable from the network whatever your NSG allows.
Step 6 - Retrieve this VM's administrator credentials
Every password, signing key and certificate on this VM was generated during its first boot. They are recorded in a root-only file.
sudo stat -c '%n %a %U:%G' /root/artifact-hub-credentials.txt
sudo grep -E '^artifacthub\.(url|admin\.email|admin\.alias)=' /root/artifact-hub-credentials.txt echo "administrator password length: $(sudo grep '^artifacthub.admin.password=' /root/artifact-hub-credentials.txt | cut -d= -f2- | wc -c) characters"Expected output (the URL is your VM's address):
/root/artifact-hub-credentials.txt 600 root:root artifacthub.url=https://4.246.179.114/ artifacthub.admin.email=admin@4.246.179.114 artifacthub.admin.alias=sac administrator password length: 25 charactersPrint the password when you are ready to sign in:
sudo grep '^artifacthub.admin.password=' /root/artifact-hub-credentials.txt | cut -d= -f2-Confirm that these credentials authenticate against the running server. The command reads them straight from the file, so you never type them on a command line:
E=$(sudo grep '^artifacthub.admin.email=' /root/artifact-hub-credentials.txt | cut -d= -f2-)
P=$(sudo grep '^artifacthub.admin.password=' /root/artifact-hub-credentials.txt | cut -d= -f2-)
JAR=$(mktemp)
TOK=$(curl -ks -c "$JAR" -D - -o /dev/null https://127.0.0.1/api/v1/csrf | tr -d '\r' | awk 'tolower($1) ~ /^x-csrf-token:/ {print $2}')
CODE=$(printf '{"email":"%s","password":"%s"}' "$E" "$P" \
| curl -ks -b "$JAR" -c "$JAR" -H "X-CSRF-Token: $TOK" -H 'Content-Type: application/json' \
-H 'Referer: https://127.0.0.1/' --data @- \
-o /dev/null -w '%{http_code}' https://127.0.0.1/api/v1/users/login)
echo "sign in : HTTP $CODE (204 = a session cookie was issued)"
PROFILE=$(curl -ks -b "$JAR" https://127.0.0.1/api/v1/users/profile) rm -f "$JAR" echo "authenticated as : $(echo "$PROFILE" | grep -o '"alias":"[^"]*"')" echo "$PROFILE" | grep -q '"alias"' || { echo "FAILED: the issued session is not usable"; exit 1; }A successful sign-in returns 204 and no body. The useful part is the session cookie it sets, so the block then uses that session to read your profile back.
Expected output:
sign in : HTTP 204 (204 = a session cookie was issued) authenticated as : "alias":"sac"After you sign in, change the password under Control Panel -> Settings -> Profile. Keep the file for the database credentials.
Step 7 - Sign in to Artifact Hub
Browse to https://<public-ip>/. The VM created its own certificate, so your browser shows a warning the first time. Accept it, or install your own certificate first with Step 12.
Select Sign in at the top right and use the email address and password from Step 6.
Self-service sign-up is switched off in the shipped configuration. Artifact Hub creates self-registered accounts with an unverified email and refuses to sign them in until a verification mail is confirmed. On an appliance with no mail server, a new user could never get in. Step 11 turns both features on together once you have an SMTP server.
Step 8 - Point Artifact Hub at your own repositories
This is what a self-hosted catalogue is for. One starter repository is registered under your administrator account so the catalogue is not empty on day one. Add your own beside it, or remove it.
In the web interface, go to Control Panel -> Repositories -> Add repository. Choose the kind (Helm charts, OLM operators, Falco rules, OPA policies, Tekton tasks and so on), enter a name and the URL your charts are published at, and save. The Add repository dialog links to the bundled documentation for each kind, served by this VM:
for d in /docs/ /docs/api/ /docs/topics/repositories/helm-charts/; do printf '%-42s HTTP %s\n' "$d" "$(curl -ks -o /dev/null -w '%{http_code}' "https://127.0.0.1$d")" doneExpected output:
/docs/ HTTP 200 /docs/api/ HTTP 200 /docs/topics/repositories/helm-charts/ HTTP 200List what is registered right now:
sudo -u postgres psql -c "SELECT name, url, last_tracking_ts IS NOT NULL AS indexed FROM repository ORDER BY name" -d hubExpected output:
name | url | indexed --------------+--------------------------------------------+--------- artifact-hub | https://artifacthub.github.io/helm-charts/ | t (1 row)To remove the starter repository, open Control Panel -> Repositories, open the row menu and choose Delete. Deleting it also removes its indexed packages.
Container image vulnerability scanning is not included in this image. Artifact Hub's security reports come from a separate upstream component that needs its own long-running Trivy server and vulnerability database. The starter repository is therefore registered with scanning switched off, so the control panel does not advertise a scan that will never run. Everything else in the catalogue, including each package's declared security context and signature metadata, works as upstream.
Before you publish, lint your repository metadata with the bundled CLI so the catalogue renders it properly:
ah version ah lint --help | head -7Expected output:
Version: 1.23.0 Git commit: f90cbb4ee79a31eaa348dbb2868ffa9d2cbe15d8 Check the repository's packages are ready for Artifact Hub Use this command to check that the packages in your repository are ready to be listed on Artifact Hub. This command checks that the packages metadata provided is valid and displays some information about the data that will be collected soStep 9 - Watch the tracker index your repositories
The tracker is a one-shot job on a 30-minute timer, which mirrors the upstream schedule. systemd does not start a second copy while one is running, so runs never overlap.
systemctl list-timers artifacthub-tracker.timer --no-pager | head -3Expected output:
NEXT LEFT LAST PASSED UNIT ACTIVATES Fri 2026-08-07 13:31:00 UTC 14min - - artifacthub-tracker.timer artifacthub-tracker.serviceRun the tracker immediately instead of waiting for the timer, then confirm that packages were indexed:
sudo systemctl start artifacthub-tracker.service echo "tracker result: $(systemctl show -p Result --value artifacthub-tracker.service)" echo "packages in the catalogue: $(sudo -u postgres psql -tAc 'SELECT count(*) FROM package' -d hub | tr -d ' ')" curl -ks 'https://127.0.0.1/api/v1/packages/search?limit=3&facets=false' | head -c 220; echoExpected output:
tracker result: success packages in the catalogue: 1 {"packages":[{"package_id":"ceb9ab96-d1dc-49cb-be94-34e2b3b83d92","name":"artifact-hub","normalized_name":"artifact-hub","logo_image_id":"019ab0f2-2b4a-47d9-abe3-cc8ddb92a4b2","stars":0,"description":"Artifact Hub is a wIf a repository reports errors, they are recorded against it and shown in the Control Panel. List them with:
sudo -u postgres psql -c "SELECT name, coalesce(last_tracking_errors,'(none)') AS errors FROM repository" -d hubExpected output:
name | errors --------------+-------- artifact-hub | (none) (1 row)Step 10 - Use the REST API with an API key
Requests that carry an API key skip the browser CSRF handshake, which makes them convenient for CI. Create a key under Control Panel -> Settings -> API keys, then send the two headers that Artifact Hub expects:
curl -k -H "X-API-KEY-ID: <your-token>" -H "X-API-KEY-SECRET: <your-token>" \
https://<public-ip>/api/v1/repositories/userAnonymous read endpoints need no key, which suits most search integrations:
curl -ks https://127.0.0.1/api/v1/packages/stats
curl -ks 'https://127.0.0.1/api/v1/packages/search?limit=1&facets=false' \
| grep -o '"packages":\[' | head -1Expected output:
{"packages" : 1, "releases" : 44} "packages":[Your VM serves the full OpenAPI reference at https://<public-ip>/docs/api.
Step 11 - Enable email and self service sign up (optional)
Sign-up and email work as one feature. Configure an SMTP server first, then allow sign-ups. Enabling sign-up on its own produces accounts that can never sign in.
Edit /etc/artifacthub/hub.yaml, uncomment the email: block and fill in your server, then set allowUserSignUp: true:
sudo nano /etc/artifacthub/hub.yaml
sudo systemctl restart artifacthub-hub.serviceA secret-free reference copy of every available setting ships alongside it:
sudo ls -l /etc/artifacthub/ echo "--- current sign up setting ---" sudo grep 'allowUserSignUp' /etc/artifacthub/hub.yamlExpected output:
total 12 -rw-r----- 1 root artifacthub 2444 Aug 7 13:10 hub.yaml -rw-r--r-- 1 root root 788 Aug 7 13:00 hub.yaml.sample -rw-r----- 1 root artifacthub 587 Aug 7 13:10 tracker.yaml --- current sign up setting --- allowUserSignUp: falseThe same file holds theme.siteName. Leave it as shipped unless you intend to rebrand. Changing it switches the front end into white-label mode, whose footer loads a logo from artifacthub.io. On a VM without outbound access to that host, the logo appears as a broken image.
Step 12 - Install your own TLS certificate
The certificate generated on first boot is valid for the VM's own addresses but is self-signed, so browsers warn. Point a DNS name at the VM and replace the certificate. nginx terminates TLS, so only these two files change:
sudo certbot certonly --standalone -d <your-domain> --agree-tos -m <your-email>
sudo cp /etc/letsencrypt/live/<your-domain>/fullchain.pem /etc/nginx/tls/artifact-hub.crt
sudo cp /etc/letsencrypt/live/<your-domain>/privkey.pem /etc/nginx/tls/artifact-hub.key
sudo chmod 600 /etc/nginx/tls/artifact-hub.key
sudo systemctl restart nginx.serviceNext, update the address that Artifact Hub uses in outbound links and in its CSRF allowlist. Edit /etc/artifacthub/hub.yaml, set server.baseURL to https://<your-domain> and add <your-domain> to server.csrf.trustedOrigins, then restart:
sudo nano /etc/artifacthub/hub.yaml
sudo systemctl restart artifacthub-hub.serviceInspect the certificate that the VM presents now:
echo | openssl s_client -connect 127.0.0.1:443 2>/dev/null | openssl x509 -noout -subject -datesExpected output:
subject=CN = 4.246.179.114, O = sac Artifact Hub appliance notBefore=Aug 7 13:10:58 2026 GMT notAfter=Aug 4 13:10:58 2036 GMTStep 13 - Check persistence and hardening
The provisioning service runs once and then disables itself, so a reboot never regenerates your passwords or wipes your catalogue.
systemctl is-enabled artifact-hub-firstboot.service 2>&1 || true
sudo test -f /var/lib/sac/data/artifact-hub-firstboot.done && echo "first boot sentinel: present"
sudo test -f /var/lib/sac/data/artifact-hub.ready && echo "bootstrap ready marker: present" echo "swap: $(swapon --show | wc -l) entries" echo "held packages: $(apt-mark showhold | wc -l)"Expected output:
disabled first boot sentinel: present bootstrap ready marker: present swap: 0 entries held packages: 0The disabled state is expected. The unit disabled itself after provisioning this VM. The server and the tracker both wait for the ready marker, so neither can start against an unprovisioned database.
Unattended security upgrades are enabled, as on stock Ubuntu:
systemctl is-enabled unattended-upgrades.service grep -h Unattended /etc/apt/apt.conf.d/20auto-upgradesExpected output:
enabled APT::Periodic::Unattended-Upgrade "1";Step 14 - Back up and maintain your VM
Your data lives in PostgreSQL and in /etc/artifacthub. Take an Azure disk snapshot of the whole VM and a logical dump of the catalogue:
sudo -u postgres pg_dump -Fc hub > /var/backups/artifact-hub-$(date +%F).dump
sudo tar czf /var/backups/artifact-hub-config-$(date +%F).tgz /etc/artifacthub /etc/nginx/tlsKeep the credentials file with those backups. It holds the database password that the configuration depends on.
To see what the appliance was built from, run:
sudo cat /usr/share/doc/sac/artifact-hub-build-manifest.txtExpected output:
Artifact Hub 1.23.0 upstream tag v1.23.0 upstream commit f90cbb4ee79a31eaa348dbb2868ffa9d2cbe15d8 LICENSE sha256 cfc7749b96f63bd31c3c42b5c471bf756814053e847c10f3eb003417bc523d30 (Apache-2.0) hub sha256 62a7713abf9aa31f00a59bf29a9b82d3e8711611de3269fdc7568f17f19f4441 tracker sha256 f86ffc64e1e377f630f19e07efe3a8ec51543262147f9fc4127cd711f3c6b337 ah sha256 998cf59797b5c4752738b88b40be35e105ab12e021f14a55cea432d221422ba8 go 1.26.4 node 24.19.0 hugo 0.111.3 libtensorflow 2.11.0 opm v1.60.0 tern v1.13.0 postgresql 16.14(Ubuntu16.14-0ubuntu0.24.04.1) schema version 64Security notes
- No credential ships in the image. The database role has no password until first boot sets one, there are no accounts, and the session cookie key and CSRF key are generated per VM. Upstream's compiled defaults for those keys are the literal strings
sample hash keyandsample auth key. Neither survives into a running VM. - The upstream demo account never exists. The schema is migrated with sample data disabled instead of being seeded and then cleaned, so
demo@artifacthub.iois never live. - Nothing is served until provisioning succeeds. The server and tracker units wait for a marker that is written only after every secret exists and the administrator has been proven able to sign in.
- PostgreSQL and the metrics endpoint listen on loopback only. Upstream binds Artifact Hub's unauthenticated Prometheus endpoint to all interfaces by default. Here it is bound to
127.0.0.1and the firewall never opens it. - The catalogue is yours. The front end calls the API on its own origin. No part of the shipped interface uses
artifacthub.ioas a backend. - Container image vulnerability scanning is not included. It is an optional upstream component with its own Trivy server and database. Repositories seeded by this image have scanning switched off, so nothing promises a report that will not arrive.
- Secrets never reach the journal. Provisioning passes every password-bearing statement to PostgreSQL on standard input instead of as a command argument, so
sudocommand logging andpsnever see them. - Restrict access. Limit
22/tcpto your management network, put443/tcpbehind your own certificate, and rotate the administrator password after your first sign-in.
Architecture summary
Component Detail
Application Artifact Hub 1.23.0, Apache 2.0, CNCF incubating project
Server /opt/artifacthub/bin/hub, artifacthub-hub.service, listening on 127.0.0.1:8000
Indexer /opt/artifacthub/bin/tracker, artifacthub-tracker.timer, every 30 minutes
CLI /usr/local/bin/ah, repository metadata linter
Database PostgreSQL 16 on the same VM, database hub, loopback only
Web front nginx, TLS on 443, permanent redirect from 80
Documentation Served by the VM at /docs, OpenAPI reference at /docs/api
Firewall nftables, default drop, 22/80/443 only
Per VM secrets Database password, cookie hash key, CSRF auth key, administrator password, TLS key
Credentials file /root/artifact-hub-credentials.txt, mode 0600 root:root
Base image Ubuntu 24.04 LTS, patched at build time, unattended security upgrades enabled
Support
SecureAnyCloud provides 24/7 support for this image. Email sales@secureanycloud.com with the offer name, your VM size and region, and the output of systemctl status artifacthub-hub.service and journalctl -u artifact-hub-firstboot.service.
Deploy on Azure
Launch Artifact Hub on Ubuntu 24.04 LTS with 24/7 support from SecureAnyCloud.
View this image
Need Help?
Our support team is available 24/7.
sales@secureanycloud.com