Screwdriver CI/CD on Ubuntu 24.04 on Azure User Guide
Overview
Screwdriver is an open-source build and continuous-delivery (CI/CD) platform, originally built at Yahoo. Pipelines are defined as code in your repository, every build step runs in an isolated container, and Screwdriver integrates with your source-control provider so commits and pull requests trigger builds automatically. The cloudimg image runs the complete single-node stack behind one nginx reverse proxy on port 80: the API, the web UI, an artifact and log store, a PostgreSQL datastore, and the Docker build executor ready to launch build containers. Backed by 24/7 cloudimg support.
What is included:
- Screwdriver API (
screwdrivercd/screwdriver:v8.0.174), web UI (screwdrivercd/ui:v1.0.1416) and artifact/log store (screwdrivercd/store:v7.0.3), run as a container stack and baked into the image (no runtime pull) - The Docker build executor with the build launcher (
screwdrivercd/launcher:v6.0.237) baked in - build steps run in isolated containers on the host Docker engine - A PostgreSQL 16 datastore whose schema is created automatically on first boot
- nginx on
:80as the single exposed surface, path-routing/to the UI,/v4to the API and/v1to the store - the containers bind the loopback interface only - A per-VM RSA JWT signing keypair and fresh secrets generated on first boot - no default login and no baked credential ship in the image
docker.service,screwdriver.serviceandnginx.serviceas systemd units, enabled and active;ufwdefault-deny with only22and80open- 24/7 cloudimg support
Screwdriver is distributed under the BSD-3-Clause licence.
Secure by default - per-VM secrets, no default login
This image ships with no default or shared login and no baked secret. On first boot a one-shot service generates a per-VM RSA-2048 JWT signing keypair, fresh cookie/encryption/hashing passwords and a fresh PostgreSQL password, resolves your VM's public address, renders the stack configuration, brings the services up and proves the artifact store's signed round-trip before writing a root-only information file. Screwdriver authenticates users through your own source-control provider (see Step 6), so there is no built-in administrator password to leak; the only signing key that can write to the store is the per-VM private key generated on your machine.

Prerequisites
An active Azure subscription, an SSH key pair, and a VNet + subnet in the target region. Standard_B2s (2 vCPU / 4 GiB RAM) is a comfortable starting point for the control plane; teams running heavy or concurrent pipelines (each build spawns its own container) should choose a larger size. NSG inbound: allow 22/tcp from your management network and 80/tcp from wherever you browse Screwdriver. You will also need an OAuth application registered with your source-control provider (GitHub, GitLab or Bitbucket) to sign in - covered in Step 6.
Step 1 - Deploy from the Azure Marketplace
Sign in to the Azure Portal, choose Create a resource, search the Marketplace for Screwdriver by cloudimg, and select Create. On Basics 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) and HTTP (80). Then Review + create -> Create.
Step 2 - Deploy from the Azure CLI
az vm create \
--resource-group <your-rg> \
--name screwdriver \
--image <marketplace-image-urn> \
--size Standard_B2s \
--admin-username azureuser \
--ssh-key-values ~/.ssh/id_ed25519.pub \
--vnet-name <your-vnet> --subnet <your-subnet> \
--public-ip-sku Standard
az vm open-port --resource-group <your-rg> --name screwdriver --port 80 --priority 1010
Step 3 - Connect to your VM
ssh azureuser@<vm-public-ip>
On first boot the stack takes a minute or two to become healthy (it generates per-VM secrets and initialises the database). The information file records the URL your Screwdriver instance is served on:
sudo grep -E '^SCREWDRIVER_URL=' /root/screwdriver-credentials.txt
Step 4 - Verify the stack is healthy
Confirm the systemd units are active and the API, UI and store all answer through nginx:
systemctl is-active docker.service screwdriver.service nginx.service
curl -s -o /dev/null -w 'API /v4/status -> HTTP %{http_code}\n' http://127.0.0.1/v4/status
curl -s -o /dev/null -w 'UI / -> HTTP %{http_code}\n' http://127.0.0.1/
curl -s -o /dev/null -w 'store /v1/status -> HTTP %{http_code}\n' http://127.0.0.1/v1/status
All three endpoints return HTTP 200, and the API also reports the running component versions:
curl -s http://127.0.0.1/v4/versions | head -c 300; echo

The four containers (API, UI, store and PostgreSQL) run under one systemd unit, and only nginx is exposed - the containers bind the loopback interface:

Step 5 - Open the web console
Browse to http://<vm-public-ip>/ (the SCREWDRIVER_URL from Step 3). The Screwdriver web console loads and prompts you to sign in through your source-control provider - Screwdriver uses your SCM to handle user authentication and to apply the same repository permissions to your pipelines.

Step 6 - Connect your source-control provider
Screwdriver signs users in and reads pipelines through an SCM OAuth application that you own. Register an OAuth app with your provider (for example, on GitHub: Settings -> Developer settings -> OAuth Apps -> New OAuth App), setting the authorization callback URL to http://<vm-public-ip>/v4/auth/login/github:github.com/web. Then edit the SCM_SETTINGS block in the stack configuration to replace the placeholder client id and secret with your OAuth app's credentials, and restart the stack:
sudo nano /opt/screwdriver/docker-compose.yml
# in the api service, set SCM_SETTINGS' oauthClientId / oauthClientSecret / secret
sudo docker compose -f /opt/screwdriver/docker-compose.yml up -d
For production use also set SECRET_WHITELIST to the list of SCM users or organisations allowed to sign in, and front nginx with TLS (for example with your own certificate or a reverse proxy) so tokens are never sent in clear text. After the restart, click Sign in with SCM Provider in the console and authorize the app.
Step 7 - Pipelines and the build API
Once signed in, you create a pipeline by pointing Screwdriver at a repository that contains a screwdriver.yaml; each push then runs the pipeline's jobs as isolated build containers via the Docker executor, with logs and artifacts stored in the built-in store. The full pipeline, job, build and event surface is available through the Screwdriver REST API, which the image serves with live documentation at http://<vm-public-ip>/v4/documentation:
curl -s -o /dev/null -w 'API docs -> HTTP %{http_code}\n' http://127.0.0.1/v4/documentation


The artifact store is authenticated with JWTs signed by the per-VM key. You can prove the signing, verification and storage path end to end with the bundled round-trip check, which mints a build token with the per-VM private key, writes an artifact (create) and reads it back (read), and confirms an unsigned token is rejected:
sudo /usr/local/sbin/screwdriver-store-roundtrip.sh
It prints OK store round-trip: PUT=202 GET=match badtoken=401.
Step 8 - Maintenance
The operating system ships fully patched with unattended security upgrades enabled, and the Docker build launcher is baked in so the executor is ready without a runtime pull:
echo "held=[$(apt-mark showhold)]"; systemctl is-enabled unattended-upgrades.service
free -m | head -2

Manage the stack with systemd and Docker Compose:
sudo systemctl status screwdriver nginx docker
sudo docker compose -f /opt/screwdriver/docker-compose.yml ps
sudo docker compose -f /opt/screwdriver/docker-compose.yml logs --tail 100 api
The PostgreSQL data and stored artifacts live under /opt/screwdriver/data; back this directory up to preserve your pipelines and build history. Because the public URL is baked into the configuration on first boot, assign your VM a static public IP or a DNS name before going to production so the console and API URLs stay stable across restarts.
Support
Every cloudimg image is backed by 24/7 support. If you have any questions about this image, contact us at support@cloudimg.co.uk.