Skip to content

Prerequisites

  • Terraform 1.6+
  • A DigitalOcean account and personal access token
  • A domain name registered with DigitalOcean DNS (or the ability to set A records)

1. Clone the Repository

bash
git clone https://github.com/phaseflag/phaseflag.git
cd phaseflag/infra/terraform

2. Create a Variables File

bash
cp terraform.tfvars.example terraform.tfvars

Edit terraform.tfvars:

hcl
# DigitalOcean API token
do_token = "dop_v1_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx"

# Domain — must be managed by DigitalOcean DNS
domain = "yourdomain.com"

# DigitalOcean region
region = "nyc3"

# Droplet size (see: doctl compute size list)
droplet_size = "s-2vcpu-4gb"

# PostgreSQL cluster size
db_size = "db-s-1vcpu-1gb"

# SSH key fingerprints to allow on the droplet
ssh_keys = ["ab:cd:ef:12:34:56:78:90:ab:cd:ef:12:34:56:78:90"]

# Deployment mode
deployment_mode = "saas"

3. Initialize Terraform

bash
terraform init

This downloads the DigitalOcean provider and initializes the backend.


4. Plan the Deployment

bash
terraform plan

Review the planned resources. Terraform will create:

ResourceDescription
digitalocean_droplet.appUbuntu 22.04 droplet for the application
digitalocean_managed_database.postgresManaged PostgreSQL 16 cluster
digitalocean_domain.mainDNS zone for your domain
digitalocean_record.apiA record: api.yourdomain.com
digitalocean_record.appA record: app.yourdomain.com
digitalocean_record.relayA record: relay.yourdomain.com
digitalocean_firewall.appFirewall rules (ports 22, 80, 443)
digitalocean_project.phaseflagDigitalOcean project to group resources

5. Apply the Deployment

bash
terraform apply

Type yes when prompted. Provisioning takes 5-10 minutes (mostly waiting for the managed database cluster to initialize).


6. Capture Outputs

After apply completes, Terraform prints the outputs:

bash
terraform output
api_url            = "https://api.yourdomain.com"
dashboard_url      = "https://app.yourdomain.com"
droplet_ip         = "192.0.2.100"
database_host      = "db-phaseflag-do-user-12345-0.g.db.ondigitalocean.com"
database_port      = 25060
database_url       = <sensitive>
connection_command = "ssh root@192.0.2.100"

View the sensitive database URL:

bash
terraform output -raw database_url

7. Post-Provisioning Setup

SSH into the droplet and run the initialization script:

bash
ssh root@$(terraform output -raw droplet_ip)

# The droplet cloud-init script installs Docker and clones the repo
# Wait for it to complete:
cloud-init status --wait

# Run migrations
cd /opt/phaseflag/infra/docker
docker compose -f docker-compose.selfhosted.yml exec api alembic upgrade head

Key Variables Reference

VariableTypeRequiredDescription
do_tokenstringYesDigitalOcean personal access token
domainstringYesDomain managed by DigitalOcean DNS
regionstringYesDigitalOcean datacenter region
droplet_sizestringNoDroplet size slug (default: s-2vcpu-4gb)
db_sizestringNoManaged DB size (default: db-s-1vcpu-1gb)
ssh_keyslistYesSSH key fingerprints for droplet access
deployment_modestringNooss, saas, or enterprise (default: oss)
enable_relayboolNoDeploy relay proxy (default: false)

Outputs Reference

OutputDescription
droplet_ipPublic IP of the application server
api_urlHTTPS URL of the API
dashboard_urlHTTPS URL of the admin dashboard
database_hostHostname of the managed PostgreSQL cluster
database_urlFull connection string (sensitive)
connection_commandSSH command to connect to the droplet

Updating Infrastructure

To change the droplet size or other configuration:

  1. Edit terraform.tfvars
  2. Run terraform plan to preview changes
  3. Run terraform apply

Destroying Infrastructure

bash
terraform destroy

Remote State (Production Recommendation)

For team use, store Terraform state in DigitalOcean Spaces (S3-compatible):

hcl
# backend.tf
terraform {
  backend "s3" {
    endpoint = "https://nyc3.digitaloceanspaces.com"
    bucket   = "your-tf-state-bucket"
    key      = "phaseflag/terraform.tfstate"
    region   = "us-east-1"  # placeholder for S3 compat

    skip_credentials_validation = true
    skip_metadata_api_check     = true
    skip_region_validation      = true
    force_path_style            = true
  }
}

Released under the Apache 2.0 License.