---
title: "Pull request previews on Vultr"
description: "Run each pull request's stagelet on its own instance in your Vultr account. See what stagely setup creates, the API key it needs and the host firewall rules."
url: https://stagely.dev/docs/setup/cloud/vultr
---

Stagely gives each GitHub pull request a stagelet: a private, running copy of your app. On Vultr, each stagelet runs on its own instance in your account. You run `stagely setup vultr` once. After that, the customer agent creates an instance 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                | `10.42.0.0/20`, for the hosts, stagelet machines and build workers                  |
| Agent host         | Instance of plan `vc2-1c-1gb`, Ubuntu 24.04, with a reserved IP as its main address |
| Gateway host       | Instance of plan `vc2-1c-1gb`, Ubuntu 24.04, with a reserved IP as its main address |
| Firewall groups    | One for each host, and one with no rules for stagelet machines and build workers    |
| Container registry | Vultr Container Registry `<region>.vultrcr.com/<registry>`, on the `start_up` plan  |
| SSH key            | The key that setup uses to install the agent and the gateway                        |

The reserved IPs stay when a host is replaced.

## Credentials

Setup needs a Vultr API key, in `VULTR_API_KEY`. The key 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 key to create and delete instances, and to get registry credentials for them. The key never goes to a machine that runs your app.

## Stagelet machines and build workers

- The agent creates one instance for each stagelet. Its plan is `vc2-2c-4gb`. Setup has no flag to change this plan.
- The agent keeps two build workers, also of plan `vc2-2c-4gb`. They stay when stagelets come and go.
- Each instance starts from Ubuntu 24.04, and cloud-init installs Docker at the first boot. With `-golden-image`, instances start from a snapshot that already has Docker. The `stagely` repository has the Packer template for this snapshot.
- Each instance joins the VPC. The agent connects to it there over SSH, and accepts only the host key that it gave the instance.
- Each instance keeps the public IPv4 address that comes with it, but only to reach the registry, package mirrors and the internet. It accepts no connections on that address.

## Registry

Build workers push images to the Vultr Container Registry that setup creates. Before each build and each start, the agent asks the Vultr API for registry credentials that expire after two hours. A build worker gets credentials that can push. A stagelet machine gets credentials that can only pull. Neither the API key nor the registry's root user goes to a machine.

## Isolation

A Vultr firewall group filters only inbound traffic on the public interface. Traffic inside a VPC passes it unfiltered. So each machine adds iptables rules at every boot, before Docker starts:

- A stagelet machine or build worker accepts new connections from the VPC only from the agent host and the gateway host.
- Containers cannot open connections to the VPC. So a stagelet cannot reach another stagelet, the agent host or the gateway host there.
- The agent host and the gateway host accept no new connections from the VPC.

On the public interfaces:

- The firewall group of stagelet machines and build workers has no rules, so they accept no connections there.
- 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 instance, containers also cannot open new connections to the metadata service at `169.254.169.254`.

## Run setup

```sh
export VULTR_API_KEY=<key>
stagely login
stagely setup vultr \
  -environment production \
  -region fra \
  -stagelet-domain stagelets.example.com \
  -registry examplestagely \
  -operator-cidr 203.0.113.7/32 \
  -binaries bin/linux-amd64 \
  -acme-email ops@example.com
```

| Flag            | Meaning                                                                                                                            |
| --------------- | ---------------------------------------------------------------------------------------------------------------------------------- |
| `-region`       | Vultr region, required                                                                                                             |
| `-registry`     | Name of the container registry to create, required. Registry names are global, and they contain only lowercase letters and digits. |
| `-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. [Point DNS at the gateway](https://stagely.dev/docs/setup/cloud.md#point-dns-at-the-gateway) with an A record.

## 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.
