# Deploy SiteWatch on DigitalOcean

This guide prepares a private pilot so a public WordPress site can reach SiteWatch over HTTPS. It does not create a DigitalOcean account, Droplet, DNS record, or paid service. Follow it only after you decide to provision a server; plan prices and backup charges can change.

For a repeatable VM and firewall baseline, see the [DigitalOcean Terraform template](../infra/digitalocean/terraform/README.md). It does not install SiteWatch or change DNS; review its inputs, plan and price before provisioning.

## Starting size and expected cost

For the first Skyza pilot, the recommended starting size is a Basic Droplet with **2 vCPU, 4 GiB RAM, and 80 GiB SSD** at **$24/month**, with 4,000 GiB outbound transfer and monthly billing. This gives more room for SiteWatch's app, database, proxy and scan workers than the smaller plan, but it has not been load-tested; monitor memory and container use before increasing scan volume or inviting subscribers. The **1 vCPU, 2 GiB RAM, 50 GiB SSD** size is **$12/month**, with 2,000 GiB outbound transfer; use it only for a low-volume, one-site experiment if the lower cost is important. Neither size is a SiteWatch production-capacity guarantee. [Droplet pricing](https://www.digitalocean.com/pricing/droplets).

DigitalOcean bills Basic Droplets per second with a 60-second minimum and a monthly cap. Powering a Droplet off does not stop billing; destroy it to stop compute charges, after preserving an off-server backup. Its optional Basic backups add 20% for weekly backups or 30% for daily backups. On the $12 pilot plan, that is about $2.40 or $3.60 per month; on the $24 plan, it is about $4.80 or $7.20. Provider backups are stored in the same datacenter and do not replace SiteWatch's encrypted backup copied off the server. [Droplet pricing and billing](https://www.digitalocean.com/pricing/droplets), [powered-off Droplet billing](https://docs.digitalocean.com/reference/doctl/reference/compute/droplet-action/power-off/), [backup pricing](https://docs.digitalocean.com/products/backups/details/pricing/), [backup features and limits](https://docs.digitalocean.com/products/backups/details/features/).

## 1. Create the Droplet securely

1. In the DigitalOcean control panel, add an SSH public key for your computer. If you do not have a key pair, create one locally with OpenSSH. Keep the private key on your computer; upload only the `.pub` file.
2. Create a Droplet with Ubuntu 24.04 LTS, the recommended 4 GiB / 2 vCPU Basic plan, a region near your users, IPv4, monitoring, and a unique tag such as `sitewatch`. Choose the 2 GiB / 1 vCPU option only for a low-volume, one-site experiment and monitor memory closely; neither size has a SiteWatch production-capacity guarantee.
3. Before creating it, expand **Additional Options → Startup scripts** and paste the cloud-init script below. DigitalOcean applies user data only during creation. The script installs Docker Engine from Docker's official Ubuntu apt repository and enables the service. It does not open ports, change SSH access, or add a user to Docker's root-equivalent group. [DigitalOcean startup scripts](https://docs.digitalocean.com/products/droplets/how-to/provide-user-data/), [Docker's Ubuntu installation steps](https://docs.docker.com/engine/install/ubuntu/).

```yaml
#cloud-config
package_update: true
packages:
  - ca-certificates
  - curl
runcmd:
  - |
    bash -Eeuo pipefail <<'SITEWATCH_DOCKER_SETUP'
    install -m 0755 -d /etc/apt/keyrings
    curl -fsSL https://download.docker.com/linux/ubuntu/gpg -o /etc/apt/keyrings/docker.asc
    chmod a+r /etc/apt/keyrings/docker.asc
    . /etc/os-release
    codename="${UBUNTU_CODENAME:-${VERSION_CODENAME:-}}"
    architecture="$(dpkg --print-architecture)"
    test -n "$codename"
    printf 'Types: deb\nURIs: https://download.docker.com/linux/ubuntu\nSuites: %s\nComponents: stable\nArchitectures: %s\nSigned-By: /etc/apt/keyrings/docker.asc\n' "$codename" "$architecture" > /etc/apt/sources.list.d/docker.sources
    apt-get update
    apt-get install -y docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin
    systemctl enable --now docker
    docker compose version
    SITEWATCH_DOCKER_SETUP
```

Wait until the Droplet is ready, then connect using the SSH key and verify the installation with `sudo docker compose version`. If it fails, inspect `/var/log/cloud-init-output.log`; do not continue to the app deployment until this check succeeds.
4. Follow DigitalOcean's [production-ready Droplet setup](https://docs.digitalocean.com/products/droplets/getting-started/recommended-droplet-setup/) to create a non-root sudo account, use SSH keys, and disable password-based root access. Test SSH using the new account before restricting or closing the initial access.
5. Create a [DigitalOcean Cloud Firewall](https://docs.digitalocean.com/products/networking/firewalls/getting-started/quickstart/) attached to the `sitewatch` tag. Allow inbound TCP 80 and 443 and UDP 443. Restrict inbound TCP 22 to your current public IP address; update the rule if that address changes. Keep outbound access enabled so Ubuntu updates, container image pulls, certificate issuance, and configured providers can work. Cloud Firewall is separate from any firewall installed inside Ubuntu.
6. Optional: enable weekly or daily Droplet backups after checking the extra cost. Keep a separate encrypted SiteWatch backup off this Droplet and verify that it can be restored.

Use SSH keys and the DigitalOcean firewall even though the Docker application itself listens only through its public Caddy proxy. Do not open SiteWatch's internal application port directly.

After the first connector heartbeat, inspect `free -h` and `docker stats --no-stream` on the Droplet and watch for memory pressure, swap growth, or out-of-memory events while a quick scan runs. If the pilot is constrained, resize the Droplet in the control panel and repeat the check. Do not add more websites, frequent scans, or subscribers based only on this smoke test.

## 2. Point a hostname at the Droplet

Choose a subdomain you control, for example `monitor.example.com`. At the DNS provider authoritative for that domain, create an **A** record pointing to the Droplet's public IPv4. Do not add an AAAA record unless public IPv6 works on the Droplet and its firewall permits the same web traffic. Wait until the hostname resolves to the new address before requesting HTTPS certificates.

## 3. Copy and prepare SiteWatch

From PowerShell on the computer with the SiteWatch checkout, copy the generated source archive to the non-root account. Replace the example values with the Droplet IP and account name:

```powershell
$VpsIp = "203.0.113.10"
$SshUser = "sitewatch-admin"
scp "C:\Users\alpha\OneDrive\Desktop\Clients\TaskLine\sitewatch\sitewatch-source.zip" "${SshUser}@${VpsIp}:sitewatch-source.zip"
```

Connect to the Droplet and unpack the archive:

```bash
sudo apt-get update
sudo apt-get install -y unzip
sudo mkdir -p /opt
sudo unzip -q "$HOME/sitewatch-source.zip" -d /opt
sudo chown -R "$USER":"$USER" /opt/sitewatch
cd /opt/sitewatch
```

The archive excludes `.env`, databases, owner keys, connector credentials, dependencies, and Git history. Keep it private because it contains unpublished source code.

## 4. Install the image scanner and start SiteWatch with HTTPS

Create the private environment file and verify that signup stays closed:

```bash
cp .env.example .env
chmod 600 .env
grep -nE '^(ALLOW_SIGNUP|PUBLIC_URL)=' .env
```

Do not add billing, email, SMS, or AI credentials during the initial setup. Install Grype and Cosign using [Anchore's official installation guidance](https://github.com/anchore/grype#installation) and enable Grype's release-signature verification. Confirm `grype version` works for the deployment account. The deploy helper builds the SiteWatch image, refreshes Grype's vulnerability database, resolves all configured image references, and scans each image with `--fail-on high`, then starts the stack only if all scans succeed. A missing scanner, scanner/database error, or high/critical finding stops deployment.

The helper builds a custom Caddy 2.11.7 image from pinned official builder/runtime images, verifies the compiled Go and `x/net` versions, and scans that final candidate before startup. The candidate has not been built or scanned on this Windows host; let the required Grype result decide whether deployment can proceed. Do not bypass a high/critical finding or scanner error.

Replace the example hostname with the DNS name you configured, then run the deployment helper:

```bash
sudo bash ./scripts/deploy-public.sh --url https://monitor.example.com
```

The helper builds the containers, starts Caddy for HTTPS, requires an HTTP 200 health response, and confirms public signup is disabled. It does not configure DNS or the DigitalOcean firewall. If deployment fails, check the A record, the TCP 80/443 and UDP 443 rules, and `docker compose -f compose.yaml -f compose.public.yaml logs caddy sitewatch`.

Open the HTTPS URL and sign in with the generated owner key. Retrieve it on the server, then save it in a password manager:

```bash
cd /opt/sitewatch
docker compose -f compose.yaml -f compose.public.yaml exec sitewatch cat /data/owner-key
```

Never put the owner key in WordPress or share it in chat. WordPress receives only its individual SiteWatch connector token.

## 5. Connect Skyza WordPress

In SiteWatch, add `https://skyza.net/` with backend reporting enabled and securely save the generated Site ID and connector token. In WordPress, open **Settings → SiteWatch**, enter the public SiteWatch HTTPS URL and those per-site values, save, and use **Send heartbeat now**. Confirm the dashboard receives a report and shows a fresh heartbeat. Do not consider the connector connected merely because the plugin is installed.

The current SiteWatch dashboard is private to the Tailscale network, and the Skyza plugin settings have not been connected. This guide has not been tested against a real Droplet, public DNS, certificate issuance, or Hostinger delivery.

## Before opening subscriptions

Keep signup disabled until the readiness panel is clear and these have been tested on the public host: a real test-mode checkout and signed webhook lifecycle, email and alert receipt, off-host backup restore, outage alerting from a separate system, cancellation and retention behavior, and security/privacy/support policies. Provider acceptance does not prove end-user receipt. Deployment on a single Droplet is not high availability; monitor it from outside DigitalOcean and maintain a tested recovery copy.

See [provider setup and launch boundaries](PROVIDERS.md), [backup and recovery](BACKUP_RECOVERY.md), and [release status](RELEASE_STATUS.md).
