Si
Developer Tools Azure

ScanCode.io on Ubuntu 24.04 on Azure User Guide

| Product: ScanCode.io 38.0.0 on Ubuntu 24.04 LTS on Azure

Overview

ScanCode.io is the AboutCode server for automated software composition analysis. You give it a codebase, an archive or a container image, choose a pipeline, and it scans every file and reports what is actually in there: the detected licences and copyright notices, the packages and their declared dependencies, and the exact file each finding came from. Results are browsable in a web interface, queryable through a REST API, and exportable as SPDX or CycloneDX so they can feed a software bill of materials.

The cloudimg image installs ScanCode.io 38.0.0 from the official published container image, pinned by digest, and runs it as a systemd service — so a working SCA server is online within minutes of launch, with authentication already switched on.

What is included:

  • ScanCode.io 38.0.0 (Apache-2.0), deployed from the official ghcr.io/aboutcode-org/scancode.io image pinned by digest so the appliance is reproducible and can never silently upgrade
  • Docker Engine (Docker CE) + the Docker Compose plugin, installed from the official Docker package repository
  • The upstream Compose topology: nginx on port 80, web (gunicorn serving the Django application), worker (the RQ pipeline worker), db (PostgreSQL 17.10) and redis (8.8.0, append-only persistence) — every image digest-pinned
  • Authentication required out of the box (SCANCODEIO_REQUIRE_AUTHENTICATION=True). ScanCode.io ships upstream with authentication off; on a public VM that would let anyone submit scan pipelines and run arbitrary workloads on your machine
  • A per-VM admin account with a generated web password and a generated REST API key, created at first boot
  • A per-VM Django SECRET_KEY and PostgreSQL password, both rotated at first boot
  • All stateful data — the PostgreSQL database, the Redis append-only file and the scan workspace — on a dedicated 64 GiB data disk mounted at /var/lib/scancodeio (Docker's data-root is relocated there), independently resizable and re-provisioned on every VM
  • A self-authored sample codebase and a one-command self-test that proves a full scan round-trip on your own instance
  • Two systemd units: scancodeio.service (a oneshot wrapper around docker compose up -d) and scancodeio-firstboot.service
  • 24/7 cloudimg support

Prerequisites

An active Azure subscription, an SSH key, and a VNet + subnet. Standard_B2s (2 vCPU / 4 GB RAM) is the recommended starting size and is enough to evaluate ScanCode.io and to scan modest codebases: the idle stack uses roughly 1.3 GB, leaving around 2.5 GB of headroom, and a 4 GiB swapfile on the data disk absorbs scan bursts.

Scanning is genuinely CPU and memory hungry, because each scan process loads a large licence index. If you intend to scan large codebases or container images, size up — Standard_B4ms (4 vCPU / 16 GB) or Standard_D4s_v5 (4 vCPU / 16 GB) are sensible next steps, and you can raise the worker's parallelism afterwards (see Step 11).

NSG inbound rules: allow 22/tcp from your management CIDR for SSH and 80/tcp from the CIDR that needs the ScanCode.io web UI and API (add 443/tcp if you enable HTTPS).

Step 1: Connect over SSH

Replace <vm-ip> with the public IP of your VM. The default login user is azureuser.

ssh azureuser@<vm-ip>

Step 2: Confirm the services are active

sudo systemctl is-active docker.service scancodeio.service

Both report active. scancodeio.service is a oneshot unit that brings the Docker Compose stack up, so active means the stack has been started.

ScanCode.io and Docker services active, with all five stack containers running

Step 3: Confirm the stack containers are running

cd /opt/scancodeio && sudo docker compose --env-file docker.env ps --format '{{.Service}}\t{{.Status}}'

You should see all five services — db, nginx, redis, web and worker — with db and redis reporting (healthy).

Step 4: Confirm the data disk is mounted

The database, the Redis append-only file and every scan workspace live on a dedicated data disk. The scan workspace is the part that grows, because it holds each uploaded and extracted codebase.

mountpoint /var/lib/scancodeio && df -h /var/lib/scancodeio

Swap is provisioned on the same disk to absorb scan bursts:

swapon --show

Step 5: Check the ScanCode.io login page

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/accounts/login/

This returns 200. Note the login path is /accounts/login/.

Because authentication is required, the REST API refuses anonymous callers — this returns 401:

curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1/api/projects/

Step 6: Read the per-VM credentials

A unique admin password, REST API key, Django secret key and database password are generated for your instance on its first boot. The user-facing ones are written to a root-only file.

sudo ls -l /root/scancodeio-credentials.txt

The file is mode 0600, owned by root. Read it with:

sudo cat /root/scancodeio-credentials.txt

It lists scancodeio.url (where to sign in), scancodeio.admin.user (admin), scancodeio.admin.pass and scancodeio.api.key. Store them somewhere safe.

The per-VM ScanCode.io credentials file, mode 0600 root-only, values redacted

Step 7: Sign in to the ScanCode.io web UI

Browse to http://<vm-ip>/ and sign in with the username admin and the scancodeio.admin.pass from Step 6. The UI and the API are served on port 80 by the bundled nginx reverse proxy.

The ScanCode.io sign-in page served on port 80

After signing in you land on the Projects list. Each project is one scan: its pipeline, its status, and the counts of packages, dependencies and resources it found.

The ScanCode.io projects list showing a completed scan

Step 8: Prove a full scan round-trip

The image ships a self-test that does the whole job end to end: it creates a project, uploads the bundled sample codebase, runs the scan_codebase pipeline to completion, and then checks that the results contain the specific licences and package the sample was authored to carry. It also confirms that anonymous API access is refused.

sudo /usr/local/sbin/scancodeio-selftest.sh

It finishes with SELFTEST_SUCCESS. This is a real scan, asserted on its content — not a liveness ping.

The ScanCode.io self-test running a real scan and asserting the detected licences

The sample codebase it scans lives at /opt/scancodeio/sample/ and is packaged at /opt/scancodeio/cloudimg-sample-codebase.zip. Every file in it was written by cloudimg specifically to exercise the scanner, with a known licence in each:

ls -l /opt/scancodeio/sample/

Step 9: Scan your own code

In the web UI choose New Project, give it a name, pick the scan_codebase pipeline, and either upload an archive or give it a download URL. The same thing over the REST API, using the API key from Step 6:

KEY=$(sudo sed -n 's/^SCANCODEIO_API_KEY=//p' /root/scancodeio-credentials.txt | head -1)
CODE=$(curl -s -o /tmp/scio-new-project.json -w '%{http_code}' \
  -H "Authorization: Token $KEY" \
  -F "name=my-first-scan-$$" \
  -F "pipeline=scan_codebase" \
  -F "execute_now=True" \
  -F "upload_file=@/opt/scancodeio/cloudimg-sample-codebase.zip" \
  http://127.0.0.1/api/projects/)
[ "$CODE" = "201" ] || { echo "FAIL: project creation returned HTTP $CODE"; cat /tmp/scio-new-project.json; exit 1; }
echo "created project HTTP $CODE"; head -c 200 /tmp/scio-new-project.json

The response includes the project uuid. Useful pipelines beyond scan_codebase: scan_single_package (one package archive), inspect_packages (dependency manifests), analyze_docker (a container image) and load_sbom (import an existing SBOM).

Step 10: Read the results

The Resources view lists every file with its detected licence expression, and the Packages view lists the packages discovered from manifests. This is the product's whole purpose — evidence of what your codebase actually contains.

ScanCode.io resources with the detected licence expression for each file

ScanCode.io discovered packages with their declared licence expression

The same data over the API — list your projects, then fetch one project's packages:

KEY=$(sudo sed -n 's/^SCANCODEIO_API_KEY=//p' /root/scancodeio-credentials.txt | head -1)
CODE=$(curl -s -o /tmp/scio-projects.json -w '%{http_code}' \
  -H "Authorization: Token $KEY" 'http://127.0.0.1/api/projects/?page_size=5')
[ "$CODE" = "200" ] || { echo "FAIL: API returned HTTP $CODE"; cat /tmp/scio-projects.json; exit 1; }
python3 -c "import json;d=json.load(open('/tmp/scio-projects.json'));print('projects:',d['count']);[print(' -',p['name'],p.get('package_count'),'packages') for p in d['results']]"

Detected licences and packages read straight from the ScanCode.io REST API

Results export as SPDX or CycloneDX for use as a software bill of materials, from the Export button in the UI or from the API's results_download endpoint with an output_format of json, xlsx, spdx or cyclonedx.

Step 11: Manage and tune the stack

The whole stack is managed by the scancodeio.service systemd unit, which wraps docker compose under /opt/scancodeio.

sudo systemctl status scancodeio.service --no-pager | head -20

To inspect logs for a specific service:

cd /opt/scancodeio && sudo docker compose --env-file docker.env logs --tail 30 worker

Configuration lives in /opt/scancodeio/docker.env. Two settings matter most if you move to a larger VM:

  • SCANCODEIO_PROCESSES — how many parallel scan processes a pipeline may use. It ships as 1 to fit 4 GB of RAM. On a larger VM, raising it to the vCPU count speeds up big scans considerably.
  • The gunicorn --workers count in /opt/scancodeio/docker-compose.yml, which ships as 2.

After editing either, restart the stack:

sudo systemctl restart scancodeio.service

The deliberate differences between this appliance and the stock upstream Compose file are documented on the VM:

cat /opt/scancodeio/README.cloudimg

Enabling HTTPS on your own domain

The appliance serves plain HTTP on port 80. To put it behind TLS, terminate HTTPS in front of ScanCode.io (an Azure Application Gateway, a load balancer, or your own reverse proxy with a certificate), point it at port 80 on this VM, and restrict the NSG so only your proxy can reach port 80.

Once TLS terminates in front of the application, harden the cookies by setting both SESSION_COOKIE_SECURE=True and CSRF_COOKIE_SECURE=True in /opt/scancodeio/docker.env and restarting the service. They ship as False because a browser refuses to send a Secure cookie over plain HTTP, which would make signing in impossible on a stock HTTP deployment.

First-boot security model

Nothing secret is baked into the image. On the first boot of every VM, scancodeio-firstboot.service generates and applies:

  • a fresh Django SECRET_KEY — this signs session cookies and CSRF tokens, so a key shared between VMs would be a real cryptographic weakness
  • a fresh PostgreSQL password
  • a fresh admin account, with both a web password and a REST API key

It writes them to /root/scancodeio-credentials.txt (mode 0600, owned by root), records /var/lib/cloudimg/scancodeio-firstboot.done so it never re-runs, and verifies before finishing that the API accepts the new key and refuses anonymous callers.

Authentication is required by default and should stay that way on any internet-facing VM: an open ScanCode.io lets anyone submit scan pipelines, which is arbitrary workload execution on your machine.

Support

Every cloudimg image comes with 24/7 support. Contact us at support@cloudimg.co.uk.