Install Shipyard on a Raspberry Pi

Shipyard is one static binary and one SQLite file. A Raspberry Pi 4 or 5 runs it comfortably.

What you need

  • Raspberry Pi 4 or 5 with 64-bit Raspberry Pi OS or Ubuntu Server
  • An SSD or a good SD card — the queue and logs are written to local disk
  • SSH access with sudo

1. Install the binary

Take shipyard-linux-arm64 from Downloads (shipyard-linux-armv7 on a 32-bit system) and copy it to the Pi:

scp shipyard-linux-arm64 pi@raspberrypi.local:/tmp/shipyard
ssh pi@raspberrypi.local
sudo install -m 0755 /tmp/shipyard /usr/local/bin/shipyard

2. Create the service user

sudo useradd --system --home-dir /var/lib/shipyard --shell /usr/sbin/nologin shipyard
sudo install -d -m 0750 -o root -g shipyard /etc/shipyard

3. Write the configuration

/etc/shipyard/.shipyard.yaml:

listen: 127.0.0.1:8080
projects:
  - id: my-app
    repository: you/my-app
    enabled: true
    issue_label: agent-ready
    release_interval: 48h
    content:
      interval: 168h
      publish_mode: pull_request
storage:
  path: /var/lib/shipyard/shipyard.db
logging:
  directory: /var/lib/shipyard/logs
runtime:
  embedded_worker: true
  api_token_env: SHIPYARD_API_TOKEN
observability:
  metrics: true
pipelines:
  project-check:
    steps:
      - id: config
        type: diagnostic
        timeout: 30s

4. Set the API token

echo "SHIPYARD_API_TOKEN=$(openssl rand -hex 32)" | sudo tee /etc/shipyard/shipyard.env > /dev/null
sudo chown root:shipyard /etc/shipyard/shipyard.env /etc/shipyard/.shipyard.yaml
sudo chmod 0640 /etc/shipyard/shipyard.env /etc/shipyard/.shipyard.yaml

5. Add the systemd unit

/etc/systemd/system/shipyard.service:

[Unit]
Description=Shipyard portfolio control plane
Wants=network-online.target
After=network-online.target

[Service]
Type=simple
User=shipyard
Group=shipyard
WorkingDirectory=/var/lib/shipyard
StateDirectory=shipyard
StateDirectoryMode=0750
EnvironmentFile=-/etc/shipyard/shipyard.env
ExecStart=/usr/local/bin/shipyard -config /etc/shipyard/.shipyard.yaml
Restart=on-failure
RestartSec=5s
TimeoutStopSec=15s
SyslogIdentifier=shipyard
UMask=0077
NoNewPrivileges=true
PrivateTmp=true
ProtectSystem=strict
ProtectHome=true
RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6

[Install]
WantedBy=multi-user.target
sudo systemctl daemon-reload
sudo systemctl enable --now shipyard

6. Check it

curl http://127.0.0.1:8080/healthz
curl http://127.0.0.1:8080/readyz
journalctl -u shipyard -f

7. Run the first pipeline

TOKEN=$(sudo sed -n 's/^SHIPYARD_API_TOKEN=//p' /etc/shipyard/shipyard.env)
curl -X POST http://127.0.0.1:8080/api/v1/runs \
  -H "Authorization: Bearer $TOKEN" \
  -H 'Content-Type: application/json' \
  -H 'Idempotency-Key: pi-first-check-001' \
  -d '{"project_id":"my-app","kind":"pipeline","task":"project-check","input":""}'

The run goes through the queue, the embedded worker picks it up and the result is in the run list:

curl -H "Authorization: Bearer $TOKEN" "http://127.0.0.1:8080/api/v1/runs?limit=1"

Open the UI from your laptop

Shipyard listens on loopback. Tunnel it:

ssh -L 8080:127.0.0.1:8080 pi@raspberrypi.local

Then open http://127.0.0.1:8080 and paste the token.

Keep the SD card alive

Cap the journal in /etc/systemd/journald.conf:

SystemMaxUse=200M

Shipyard's own log and run retention is set under retention: in the configuration.