GUAC 1.1 on Ubuntu 24.04 on Azure User Guide
Overview
GUAC (Graph for Understanding Artifact Composition) is an OpenSSF project that turns the scattered metadata about your software supply chain into a single, connected, queryable graph. It ingests the documents your teams already produce — SBOMs in CycloneDX or SPDX form, SLSA build attestations, and vulnerability reports — and normalises them into a common model of packages, sources, builders and the dependencies between them. Instead of grepping across a pile of SBOM files, you ask questions of the graph over GraphQL: which artifacts depend on a given package, what versions are running where, which builds are affected by a newly disclosed vulnerability.
The cloudimg image installs the pinned upstream v1.1.0 release binaries (each verified by SHA-256), runs the GUAC GraphQL server and the collectsub service as hardened systemd units, and backs them with a local PostgreSQL 16 using GUAC's ent (SQL) backend — the light, self-contained backend that fits a 2 vCPU / 4 GiB VM, with no Neo4j to operate. The GraphQL API and its interactive GraphiQL Playground are reached through nginx enforcing HTTP Basic authentication on a single routable port; a deny-by-default host firewall keeps the GraphQL server, the collectsub service and PostgreSQL unreachable off-box. At first boot, guac-firstboot.service generates this VM's own PostgreSQL, database and web credentials, brings the services up, and ingests a bundled sample SBOM so the graph is already populated and queryable. There is no shared or default login in the image.
What is included:
- GUAC v1.1.0 Go binaries —
guacgql(GraphQL server),guaccsub(collectsub),guacone(ingest CLI), all pinned by SHA-256 (Apache-2.0) - PostgreSQL 16 from the official PGDG repository, bound to loopback, using GUAC's ent backend (auto-migrated on first start)
guacgql.service+guac-collectsub.servicerunning as theguacuser, gated on the first-boot markerguac-firstboot.servicegenerating the per-VM database + web credentials and ingesting the sample SBOM- GraphQL API + GraphiQL Playground on loopback (
127.0.0.1:8080) fronted by nginx Basic auth on port 80 - A ufw host firewall: deny-by-default, allowing only SSH (22) and the web port (80)
- Ubuntu 24.04 LTS base, latest security patches applied at build time
- 24/7 cloudimg support, 24h response SLA
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet + subnet. Recommended VM size: Standard_B2s (2 vCPU / 4 GiB) for evaluation and single-team use; scale up for larger graphs. You will reach the GraphQL API and Playground over HTTP on port 80, protected by per-VM Basic authentication.
Step 1: Deploy from the Azure Portal
Search the Azure Marketplace for GUAC 1.1 on Ubuntu 24.04. Create the VM with an NSG that allows:
- TCP 22 (SSH) from your management network
- TCP 80 (the GraphQL API + Playground) from your admin network
The GraphQL server (8080), the collectsub service (2782) and PostgreSQL (5432) are never exposed — the on-host ufw firewall blocks them and nginx is the sole entry point.
Step 2: Deploy from the Azure CLI
RG="guac-prod"; LOCATION="eastus"; VM_NAME="guac-01"
GALLERY_IMAGE_ID="/subscriptions/<sub-id>/resourceGroups/azure-cloudimg/providers/Microsoft.Compute/galleries/cloudimgGallery/images/guac-ubuntu-24-04/versions/<version>"
SSH_KEY="$(cat ~/.ssh/id_rsa.pub)"
az group create --name "$RG" --location "$LOCATION"
az network vnet create -g "$RG" --name guac-vnet --address-prefix 10.106.0.0/16 --subnet-name guac-subnet --subnet-prefix 10.106.1.0/24
az network nsg create -g "$RG" --name guac-nsg
az network nsg rule create -g "$RG" --nsg-name guac-nsg --name allow-ssh --priority 100 \
--source-address-prefixes "<your-mgmt-cidr>" --destination-port-ranges 22 --access Allow --protocol Tcp
az network nsg rule create -g "$RG" --nsg-name guac-nsg --name allow-web --priority 110 \
--source-address-prefixes "<your-admin-cidr>" --destination-port-ranges 80 --access Allow --protocol Tcp
az vm create -g "$RG" --name "$VM_NAME" --image "$GALLERY_IMAGE_ID" \
--size Standard_B2s --storage-sku StandardSSD_LRS \
--admin-username azureuser --ssh-key-values "$SSH_KEY" \
--vnet-name guac-vnet --subnet guac-subnet --nsg guac-nsg --public-ip-sku Standard
Step 3: Connect via SSH
ssh azureuser@<vm-ip>
Step 4: Verify the Services
Confirm first boot completed and the GraphQL server, collectsub service, PostgreSQL and nginx are all running:
systemctl is-active postgresql guacgql guac-collectsub nginx
test -f /var/lib/cloudimg/guac-firstboot.done && echo "first boot: complete"

Step 5: Review the Security Posture
GUAC's GraphQL server and collectsub service bind all interfaces, and the GraphQL /query endpoint is unauthenticated and mutating, so the appliance is locked down two ways: a deny-by-default ufw firewall that opens only SSH and the web port, and nginx Basic auth in front of the loopback GraphQL server. Confirm both:
sudo ufw status | grep ALLOW
ss -tln | grep -E ':22 |:80 |:8080|:2782|:5432'
Only ports 22 and 80 are allowed inbound; PostgreSQL is bound to 127.0.0.1 only.

Step 6: Retrieve the Per-VM Credentials
Each VM mints its own PostgreSQL, database and web (Basic auth) credentials at first boot. Read them from the root-only file:
sudo stat -c '%a %U:%G' /root/guac-credentials.txt
sudo grep -E '^(web|postgres|guac_db)\.' /root/guac-credentials.txt
# --- The GUAC GraphQL Playground + API (via nginx Basic auth on port 80) ---
web.url=<GUAC_URL>
web.user=guac
web.password=<BASIC_PASSWORD>
# --- PostgreSQL superuser + guac DB role (LOOPBACK ONLY) ---
postgres.password=<POSTGRES_PASSWORD>
guac_db.password=<GUAC_DB_PASSWORD>
guac_db.database=guac

Step 7: Verify Authentication and the Graph End to End
The routable API enforces Basic auth: an anonymous request is refused, and the per-VM credential succeeds. The bundled guac-selfcheck proves the whole appliance — firewall, loopback binding, auth, and that a real query returns the ingested graph:
WEBPW=$(sudo grep '^web.password=' /root/guac-credentials.txt | cut -d= -f2-)
curl -s -o /dev/null -w 'anonymous /query -> HTTP %{http_code}\n' http://127.0.0.1/query -X POST -H 'content-type: application/json' --data '{"query":"{__typename}"}'
curl -s -o /dev/null -w 'authenticated / -> HTTP %{http_code}\n' -u "guac:$WEBPW" http://127.0.0.1/
sudo guac-selfcheck | tail -1
Expected: anonymous /query -> 401, authenticated / -> 200, and a self-check line confirming the graph returned the ingested package nodes.

Step 8: Open the GraphQL Playground
Open http://<vm-ip>/ in your browser. Your browser prompts for the Basic credential — sign in as guac with the password from Step 6. You land in the interactive GraphiQL Playground, where you can write and run GraphQL queries against your supply-chain graph:

Step 9: Query the Supply-Chain Graph
The image ships with a sample SBOM already ingested, so the graph is populated the moment you open it. Run this query to list every package in the graph with its namespace and name:
{ packages(pkgSpec: {}) { type namespaces { namespace names { name } } } }
The result pane returns the real package nodes parsed from the sample SBOM — a generic application, and deb packages openssl and curl:

Expand the query to include versions and qualifiers to see the full detail GUAC extracted from each package URL:
{ packages(pkgSpec: {}) { type namespaces { namespace names { name versions { version qualifiers { key value } } } } } }

Step 10: Filter the Graph by Ecosystem
Every query accepts a pkgSpec filter, so you can scope questions to a package type, namespace, name or version. Filter to just the Debian packages:
{ packages(pkgSpec: {type: "deb"}) { type namespaces { namespace names { name } } } }

Step 11: Ingest Your Own Documents
Add your own SBOM, SLSA or vulnerability documents to the graph with the guacone CLI, which parses and ingests directly through the GraphQL API — no message broker required. It auto-detects CycloneDX, SPDX and SLSA formats. Re-ingest the bundled sample (or point it at your own file or directory):
sudo -u guac env HOME=/var/lib/guac guacone collect files \
--gql-addr http://localhost:8080/query /usr/share/guac/samples/
For continuous, watched collection across many sources, GUAC also ships the guaccollect + guacingest pipeline (with a NATS broker); those are out of scope for this single-node appliance but can be layered on a larger VM.
Step 12: Managing the Services
sudo systemctl status guacgql --no-pager
sudo systemctl restart guacgql
sudo journalctl -u guacgql -n 50 --no-pager
The GraphQL server reads /etc/guac/guac.yaml (which holds the per-VM database DSN); the graph lives in the local guac PostgreSQL database. Back up the database to preserve your ingested graph.
Step 13: Security Recommendations
- Restrict the NSG. Expose port 80 only to your admin network and keep SSH (22) limited to management ranges. The on-host
ufwfirewall already denies everything else by default. - Front with TLS. For production, terminate HTTPS (for example with your own certificate in nginx, or an Azure Application Gateway / Front Door) so the Basic credential and query traffic are encrypted in transit.
- Rotate the web credential and add further htpasswd users as needed (
sudo htpasswd /etc/nginx/guac.htpasswd <name>), rather than sharing theguaclogin. - PostgreSQL and the GraphQL server are never exposed directly — they bind loopback / are firewalled, and the only off-box path to the graph is through the nginx Basic-auth proxy.
Step 14: Support and Licensing
GUAC is licensed under the Apache License 2.0. The complete source is available from the upstream project at github.com/guacsec/guac. The Apache-2.0 licence text ships in the image at /usr/share/doc/guac/LICENSE. PostgreSQL (PostgreSQL License) and nginx (2-clause BSD) are likewise permissive and ship unmodified.
Need Help?
cloudimg images come with 24/7 support and a 24-hour response SLA. Contact support through the Azure Marketplace listing or at cloudimg.co.uk.