---
title: "Pull request previews on DigitalOcean"
description: "Run each pull request's stagelet on its own droplet in your DigitalOcean account. See what stagely setup creates, the token it needs and the firewalls."
url: https://stagely.dev/docs/setup/cloud/digitalocean
---

Stagely gives each GitHub pull request a stagelet: a private, running copy of your app. On DigitalOcean, each stagelet runs on its own droplet in your account. You run `stagely setup digitalocean` once. After that, the customer agent creates a droplet when a pull request deploys, and deletes it when the stagelet ends.

## What setup creates

Setup applies a Terraform module with OpenTofu or Terraform. It creates these resources in the region that you choose:

| Resource           | Details                                                                                                          |
| ------------------ | ---------------------------------------------------------------------------------------------------------------- |
| VPC                | `<environment>-stagely`, `10.42.0.0/20`. The hosts, stagelet machines and build workers join it.                 |
| Agent host         | Droplet `<environment>-agent`, size `s-1vcpu-1gb`, Ubuntu 24.04                                                  |
| Gateway host       | Droplet `<environment>-gateway`, size `s-1vcpu-1gb`, Ubuntu 24.04, with a reserved IP                            |
| Cloud firewalls    | One for each host, and one for stagelet machines and build workers, which carry the tag `<environment>-stagelet` |
| Container registry | DigitalOcean Container Registry with the name in `-registry`, on the `basic` tier                                |
| SSH key            | `<environment>-stagely-setup`, the key that setup uses to install the agent and the gateway                      |
| DNS record         | Only with `-dns-domain`: an A record for `*.<stagelet domain>` that points at the gateway's reserved IP          |

DigitalOcean allows one container registry in each account. If your account already has one, pass its name in `-registry` and add `-create-registry=false`.

## Credentials

Setup needs an API token with write access, in `DIGITALOCEAN_TOKEN`. The token stays on your computer and on the agent host. On the agent host, it is in `/etc/stagely/stagely-agent.env`, which only root can read. The agent uses the token to create and delete droplets, and to get registry credentials for them. The token never goes to a droplet that runs your app.

## Stagelet machines and build workers

- The agent creates one droplet for each stagelet. Its size is `s-2vcpu-4gb`. Setup has no flag to change this size.
- The agent keeps two build workers, also of size `s-2vcpu-4gb`. They stay when stagelets come and go.
- Each droplet starts from Ubuntu 24.04, and cloud-init installs Docker at the first boot. With `-golden-image`, droplets start from a snapshot that already has Docker. The `stagely` repository has the Packer template for this snapshot.
- Each droplet joins the VPC. The agent connects to it on its private address over SSH, and accepts only the host key that it gave the droplet.

## Registry

Build workers push images to your DigitalOcean Container Registry. Before each build and each start, the agent asks the DigitalOcean API for Docker credentials that expire after two hours. A build worker gets credentials that can push. A stagelet droplet gets credentials that can only pull.

## Isolation

- The firewall of stagelet machines and build workers accepts connections only from the agent host and the gateway host. A stagelet does not accept connections from another stagelet.
- The agent host accepts only SSH, and only from your `-operator-cidr` ranges.
- The gateway host accepts ports 80 and 443 from anywhere, and SSH from your ranges.
- On each droplet, containers cannot open new connections to the metadata service at `169.254.169.254`. This service holds the droplet's user data, which includes its host key.

## Run setup

```sh
export DIGITALOCEAN_TOKEN=<token>
stagely login
stagely setup digitalocean \
  -environment production \
  -region fra1 \
  -stagelet-domain stagelets.example.com \
  -registry example-stagely \
  -operator-cidr 203.0.113.7/32 \
  -binaries bin/linux-amd64 \
  -acme-email ops@example.com
```

| Flag               | Meaning                                                                                                                                                                                                                              |
| ------------------ | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
| `-region`          | DigitalOcean region, required                                                                                                                                                                                                        |
| `-registry`        | Container registry name, required                                                                                                                                                                                                    |
| `-create-registry` | Default `true`. Set `-create-registry=false` when the DigitalOcean account already has a registry.                                                                                                                                   |
| `-dns-domain`      | Domain in DigitalOcean DNS that is a parent of the stagelet domain, such as `example.com` for `stagelets.example.com`. Setup then adds the DNS record. For any other domain, setup adds no record and prints no reminder to add one. |
| `-golden-image`    | Snapshot id of a golden image for stagelet machines and build workers                                                                                                                                                                |

Setup also takes the [flags that every cloud takes](https://stagely.dev/docs/setup/cloud.md#flags-every-cloud-takes). At the end, it prints the gateway's reserved IP. Without `-dns-domain`, [point DNS at the gateway](https://stagely.dev/docs/setup/cloud.md#point-dns-at-the-gateway) yourself.

## After setup

1. [Add `.stagely.yml`](https://stagely.dev/docs/app/stagely-yml.md) to your repository.
2. [Add config values and secrets](https://stagely.dev/docs/setup/config.md).
3. Read the [Security model](https://stagely.dev/docs/security.md) before you add real secrets.
