SecObserve on Ubuntu 24.04 on Azure User Guide
Overview
SecObserve is an open source vulnerability management system for software development teams and cloud environments. It gathers results from a wide range of security tools into one place: findings from static analysis, dependency and container scanning, secret detection, infrastructure as code checks and dynamic testing are imported as observations, then triaged, assessed and tracked per product and branch through a modern web interface. Components and licenses can be tracked from SBOMs, assessments can be automated with rules and security gates, metrics dashboards show risk over time, and results export as VEX documents in CSAF, OpenVEX and CycloneDX formats. The cloudimg image runs SecObserve the officially supported way, as the upstream backend and frontend containers alongside a bundled PostgreSQL, orchestrated by Docker Compose under systemd and fronted by nginx on one origin. Every image is pinned by digest and captured into the VM, so your instance starts in seconds. A unique admin password, Django secret key, field encryption key and database password are generated for each VM on first boot, before the port answers, so the upstream default credentials never exist at any point. Backed by 24/7 cloudimg support.
SecObserve is a trademark of its respective owner. This image is produced by cloudimg and is not affiliated with, endorsed by, or sponsored by SecObserve or MaibornWolff. It ships the free and open source SecObserve stack, unmodified, under its 3 clause BSD style license.

What is included:
- SecObserve v1.56.0 (backend and frontend), the full open source stack, pinned by image digest
- A bundled PostgreSQL 15 database, pinned by image digest and reachable only inside a private Docker network (never published to a host port)
- Docker Engine (Docker CE) with the backend and frontend published to the loopback interface only, fronted by nginx on port
80on a single origin secobserve.service,secobserve-firstboot.serviceandnginx.serviceas systemd units, enabled and active on boot- A unique admin password, Django secret key, field encryption key and database password generated per VM on first boot, never baked into the image
- A clean, empty instance on first boot: no default account, no shipped secret, no prior data
- 24/7 cloudimg support
Prerequisites
An active Azure subscription, an SSH key pair, and a VNet plus subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a sensible starting point; increase the size for larger teams and heavy scanner import traffic. NSG inbound: allow 22/tcp from your management network and 80/tcp (and 443/tcp once you add TLS) for the web interface. SecObserve serves plain HTTP on port 80; for production, put it behind TLS with your own domain (see the final section).
Step 1 - Deploy from the Azure Marketplace
- In the Azure portal, choose Create a resource and search the Marketplace for the cloudimg SecObserve offer.
- Select the plan, then Create.
- On the Basics tab pick your subscription, resource group and region, name the VM, and select Standard_B2s (or larger).
- Choose SSH public key authentication with admin username
azureuserand provide your public key. - On the Networking tab, allow inbound
22/tcpfrom your management network and80/tcpfor the web interface. - Review and create. When the VM is running, note its public IP address.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group my-secobserve-rg \
--name secobserve \
--image cloudimg:secobserve:default:latest \
--size Standard_B2s \
--admin-username azureuser \
--generate-ssh-keys \
--public-ip-sku Standard
az vm open-port --resource-group my-secobserve-rg --name secobserve --port 80 --priority 900
The second command opens the web interface port (SSH is opened by default).
Step 3 - Connect to your VM
Replace <vm-ip> with your VM's public IP address:
ssh azureuser@<vm-ip>
Step 4 - Confirm the services are running
SecObserve, its bundled PostgreSQL, and nginx all come up automatically on first boot. Confirm they are active:
sudo systemctl is-active docker secobserve nginx
Expected output:
active
active
active
The stack runs as three containers on a private Docker network. PostgreSQL is never published to a host port; the backend and frontend are published only to the loopback interface, with nginx in front on port 80 routing /api to the backend and everything else to the web interface:
sudo docker compose --env-file /etc/secobserve/secobserve.env -f /etc/secobserve/compose.yaml ps

Step 5 - Secure by default: no default credential ever exists
The upstream SecObserve compose examples ship with a default admin / admin login, a known Django secret key and a known field encryption key. On a public server those are a serious risk. The cloudimg image closes this: on first boot, before the port answers, a unique admin password, Django secret key, field encryption key and database password are generated for this VM, and the admin user is created directly with the per VM password. The defaults never exist at any point.
You can see the whole posture at a glance. Authentication with a blank password, a wrong password, or the upstream default admin / admin is rejected, and only the per VM admin password is accepted:

Read your unique credentials (they are written to a root only file):
sudo cat /root/secobserve-credentials.txt
The file holds the instance URL, the admin username (admin) and the per VM admin password. Keep it safe.

Step 6 - Sign in to SecObserve
Open http://<vm-ip>/ in your browser. You are met with the SecObserve login screen: there is no anonymous access. Sign in as admin with the password from the credentials file.

Step 7 - The dashboard
After signing in you land on the metrics dashboard. It shows the severities and status of active observations across your products, refreshed every five minutes, with a table of the most recent active observations. The left sidebar is your home for product groups, products, observations, components, reviews, notifications and administration.

Step 8 - Create your first product
A product in SecObserve represents an application, service or repository whose findings you want to track. Create one from the web interface (Products, then Add product) or through the REST API. The API authenticates with the same per VM admin credential; the block below reads it straight from the credentials file:
PASS=$(sudo grep '^SECOBSERVE_ADMIN_PASSWORD=' /root/secobserve-credentials.txt | cut -d= -f2-)
JWT=$(curl -s -X POST http://127.0.0.1/api/authentication/authenticate/ \
-H 'Content-Type: application/json' \
-d "{\"username\": \"admin\", \"password\": \"${PASS}\"}" | jq -r '.jwt')
curl -s -X POST http://127.0.0.1/api/products/ \
-H "Authorization: JWT ${JWT}" -H 'Content-Type: application/json' \
-d '{"name": "petstore"}' | jq '{name}'
Expected output:
{
"name": "petstore"
}
Step 9 - Import scanner findings through the REST API
SecObserve imports results from many scanners (Trivy, Semgrep, Gitleaks, Grype, Checkov, KICS, Bandit, ESLint, ZAP, OSV and more), either as file uploads, directly from a scanner's API, or from your CI pipelines with the upstream GitHub Actions and GitLab CI templates. It also accepts its own generic JSON format, which the block below uses to import three sample findings into the petstore product:
cat > /tmp/sample-findings.json <<'JSON'
{
"format": "SecObserve",
"observations": [
{
"title": "CVE-2021-44228 (Log4Shell) in log4j-core",
"vulnerability_id": "CVE-2021-44228",
"parser_severity": "Critical",
"description": "Apache Log4j2 JNDI features do not protect against attacker controlled LDAP endpoints, allowing remote code execution.",
"recommendation": "Upgrade log4j-core to 2.17.1 or later.",
"origin_component_name": "log4j-core",
"origin_component_version": "2.14.1",
"cvss3_score": 10.0,
"scanner": "sample-import"
},
{
"title": "CVE-2023-44487 (HTTP/2 rapid reset) in netty",
"vulnerability_id": "CVE-2023-44487",
"parser_severity": "High",
"description": "The HTTP/2 protocol allows a denial of service because request cancellation can reset many streams quickly.",
"recommendation": "Upgrade netty to 4.1.100.Final or later.",
"origin_component_name": "netty",
"origin_component_version": "4.1.94.Final",
"cvss3_score": 7.5,
"scanner": "sample-import"
},
{
"title": "Hardcoded credential in application.properties",
"parser_severity": "Medium",
"description": "A database password is committed in plain text in src/main/resources/application.properties.",
"recommendation": "Move the credential to a secrets manager and rotate it.",
"origin_source_file": "src/main/resources/application.properties",
"origin_source_line_start": 12,
"scanner": "sample-import"
}
]
}
JSON
PASS=$(sudo grep '^SECOBSERVE_ADMIN_PASSWORD=' /root/secobserve-credentials.txt | cut -d= -f2-)
JWT=$(curl -s -X POST http://127.0.0.1/api/authentication/authenticate/ \
-H 'Content-Type: application/json' \
-d "{\"username\": \"admin\", \"password\": \"${PASS}\"}" | jq -r '.jwt')
curl -s -X POST http://127.0.0.1/api/import/file_upload_observations_by_name/ \
-H "Authorization: JWT ${JWT}" \
-F file=@/tmp/sample-findings.json -F product_name=petstore | jq
Expected output:
{
"observations_new": 3,
"observations_updated": 0,
"observations_resolved": 0
}
List the imported observations with their severities:
PASS=$(sudo grep '^SECOBSERVE_ADMIN_PASSWORD=' /root/secobserve-credentials.txt | cut -d= -f2-)
JWT=$(curl -s -X POST http://127.0.0.1/api/authentication/authenticate/ \
-H 'Content-Type: application/json' \
-d "{\"username\": \"admin\", \"password\": \"${PASS}\"}" | jq -r '.jwt')
curl -s "http://127.0.0.1/api/observations/?product_name=petstore" \
-H "Authorization: JWT ${JWT}" | jq -r '.results[] | "\(.current_severity)\t\(.title)"'
Expected output:
Critical CVE-2021-44228 (Log4Shell) in log4j-core
High CVE-2023-44487 (HTTP/2 rapid reset) in netty
Medium Hardcoded credential in application.properties
The same findings are now visible in the web interface under Observations, ready to triage. Selecting one opens the full observation with its description, recommendation, CVSS score, origins and assessment log:

In real projects you rarely import by hand: point your CI pipelines at the same import API with the upstream GitHub Actions or GitLab CI templates, or upload scanner report files directly. See the SecObserve documentation for the full list of supported parsers and the importer configuration.
Step 10 - Check the stack health from the command line
SecObserve exposes an unauthenticated liveness endpoint that is handy for monitoring and load balancer probes:
curl -si http://127.0.0.1/api/status/health/ | head -n 1
Expected output (a healthy stack):
HTTP/1.1 200 OK

Step 11 - The interactive API documentation
The full REST API is documented interactively on the VM itself, at http://<vm-ip>/api/oa3/swagger-ui. Every endpoint used in this guide (authentication, products, imports, observations) is listed there, and authenticated requests can be tried straight from the browser after clicking Authorize and pasting JWT <your-token>.
Step 12 - Invite your team
Add colleagues from Administration, Users while signed in as admin. SecObserve has fine grained authorization: users become members of products or product groups with roles from Reader to Owner, and authorization groups can map users in bulk. To rotate the admin password, change it under the user menu, or run sudo docker compose -f /etc/secobserve/compose.yaml exec backend python manage.py changepassword admin on the VM.
Step 13 - Production: your own domain with TLS
SecObserve serves plain HTTP on port 80. For production, point a DNS record at the VM, add TLS, and allow the new hostname. A common approach is to add a certificate to nginx with Certbot:
sudo apt-get install -y certbot python3-certbot-nginx
sudo certbot --nginx -d secobserve.example.com
Then append your domain to ALLOWED_HOSTS and https://secobserve.example.com to CORS_ALLOWED_ORIGINS in /etc/secobserve/secobserve.env, and restart the stack with sudo systemctl restart secobserve. The application uses a relative API base, so it follows whatever hostname serves it.
Support
Every cloudimg image includes 24/7 support. If you have any questions about deploying or operating SecObserve on Azure, contact the cloudimg team through the Azure Marketplace listing or at www.cloudimg.co.uk.