Deployment guide

ScanCode.io

Overview

ScanCode.io is the AboutCode server for automated software composition analysis. Give it a codebase, an archive or a container image, choose a pipeline, and it scans every file. It reports the detected licences and copyright notices, the packages and their declared dependencies, and the exact file each finding came from. You can browse results in a web interface, query them through a REST API, and export them as SPDX or CycloneDX to feed a software bill of materials.

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

What you get:

  • 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) and the Docker Compose plugin, installed from the official Docker package repository
  • The upstream Compose topology, with every image digest-pinned: 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)
  • Authentication required out of the box (SCANCODEIO_REQUIRE_AUTHENTICATION=True). ScanCode.io ships upstream with authentication off, which on a public VM 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, and the disk is 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 SecureAnyCloud support

Prerequisites

You need an active Azure subscription, an SSH key, and a VNet and subnet.

Standard_B2s (2 vCPU / 4 GB RAM) is the recommended starting size. It is enough to evaluate ScanCode.io and to scan modest codebases. The idle stack uses roughly 1.3 GB, which leaves around 2.5 GB of headroom, and a 4 GiB swapfile on the data disk absorbs scan bursts.

Scanning is CPU and memory hungry, because each scan process loads a large licence index. To scan large codebases or container images, use a larger size. Standard_B4ms (4 vCPU / 16 GB) and Standard_D4s_v5 (4 vCPU / 16 GB) are sensible next steps. You can then raise the worker's parallelism (see Step 11).

In the NSG, allow inbound 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.

1

Connect over SSH

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

ssh azureuser@<vm-ip>
2

Confirm the services are active

sudo systemctl is-active docker.service scancodeio.service

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

3

Confirm the stack containers are running

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

All five services should appear: db, nginx, redis, web and worker. The db and redis services report (healthy).

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 grows the most, 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. Check it with:

swapon --show
5

Check the ScanCode.io login page

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

The command returns 200. The login path is /accounts/login/.

Authentication is required, so the REST API refuses anonymous callers. The following command returns 401:

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

Read the per-VM credentials

Your instance's first boot generates a unique admin password, REST API key, Django secret key and database password. The user-facing credentials are written to a root-only file.

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

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

sudo cat /root/scancodeio-credentials.txt

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

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 bundled nginx reverse proxy serves the UI and the API on port 80.

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

8

Prove a full scan round-trip

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

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

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

The sample codebase it scans lives at /opt/scancodeio/sample/ and is packaged at /opt/scancodeio/sac-sample-codebase.zip. SecureAnyCloud wrote every file in it specifically to exercise the scanner, with a known licence in each. List the files with:

ls -l /opt/scancodeio/sample/
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. To do the same over the REST API, use 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/sac-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. Other useful pipelines beyond scan_codebase are scan_single_package (one package archive), inspect_packages (dependency manifests), analyze_docker (a container image) and load_sbom (import an existing SBOM).

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. Together they show evidence of what your codebase actually contains.

You can read the same data over the API. The following commands list your projects and print their package counts:

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']]"

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

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 sets 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 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. Read them with:

cat /opt/scancodeio/README.sac

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 with 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. Set both SESSION_COOKIE_SECURE=True and CSRF_COOKIE_SECURE=True in /opt/scancodeio/docker.env and restart 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. It 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.

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

Authentication is required by default. Keep it on for any internet-facing VM, because an open ScanCode.io lets anyone submit scan pipelines, which is arbitrary workload execution on your machine.

Support

Every SecureAnyCloud image comes with 24/7 support. To reach the team, email sales@secureanycloud.com.

Deploy on Azure

Launch ScanCode.io 38.0.0 on Ubuntu 24.04 LTS on Azure with 24/7 support from SecureAnyCloud.

View this image

Need Help?

Our support team is available 24/7.

sales@secureanycloud.com

Deploy on Azure