Files
Leon Xu d21c6a73f1
tfvalidate / list terraform addon directories (push) Waiting to run
tfvalidate.yml / terraform validate (${{ fromJson(needs.listaddons.outputs.matrix) }}) (push) Blocked by required conditions
doc: added BYO image for DigitalOcean
2026-08-07 19:02:34 -07:00
..
2026-08-07 18:48:08 -07:00

Terraform DigitalOcean Fleet Deployment

This Terraform project automates the deployment of Fleet Device Management (Fleet) on DigitalOcean. It provisions all infrastructure components using DigitalOcean's managed services:

  • App Platform — Runs the Fleet application container (equivalent to AWS ECS or GCP Cloud Run)
  • Managed MySQL — Database for Fleet (equivalent to AWS Aurora or GCP Cloud SQL)
  • Managed Valkey — Redis-compatible cache (equivalent to AWS ElastiCache or GCP Memorystore)
  • Spaces — S3-compatible object storage for software installers (equivalent to AWS S3 or GCP GCS)
  • VPC — Private networking between all resources
  • DNS — Domain and CNAME record for Fleet
  • Database Firewalls — Restricts database and cache access to the App Platform app only

Using Your Own Image

By default, the module deploys the official fleetdm/fleet image from Docker Hub. To deploy your own image (e.g., a custom Fleet build), set image_tag in fleet_config to a full image reference:

Docker Hub (public)

fleet_config = {
  image_tag = "your-org/your-fleet:v1.0.0"
  # ...
}

Docker Hub (private)

fleet_config = {
  image_tag                  = "your-org/your-fleet:v1.0.0"
  image_registry_credentials = "your-username:your-access-token" # Don't commit this!
  # ...
}

Pass the credentials at deploy time instead of committing them:

terraform apply -var-file="fleet-10.tfvars" \
  -var="fleet_config={image_tag:\"your-org/your-fleet:v1.0.0\",image_registry_credentials:\"$DOCKERHUB_USER:$DOCKERHUB_TOKEN\",instance_size_slug:\"basic-xs\",instance_count:1,debug_logging:false,exec_migration:true}"

DigitalOcean Container Registry (DOCR)

Push your image to DOCR first:

# One-time: create a registry and log in
doctl registry create my-registry
doctl registry login

# Build, tag, and push your image
docker build -t registry.digitalocean.com/my-registry/fleet:v1.0.0 .
docker push registry.digitalocean.com/my-registry/fleet:v1.0.0

Then reference it:

fleet_config = {
  image_tag            = "registry.digitalocean.com/my-registry/fleet:v1.0.0"
  image_deploy_on_push = true  # Optional: redeploy automatically on every push
  # ...
}

The module detects the registry automatically from the image_tag prefix — anything starting with registry.digitalocean.com/ uses DOCR, everything else uses Docker Hub. The migration job uses the same image.

Prerequisites

  1. Terraform: Version ~> 1.11. Install from terraform.io.
  2. DigitalOcean Account: With a payment method configured.
  3. DigitalOcean API Token: Set as the DIGITALOCEAN_TOKEN environment variable.
    export DIGITALOCEAN_TOKEN="dop_v1_your_token_here"
    
  4. DigitalOcean Spaces Keys: Set as environment variables.
    export SPACES_ACCESS_KEY_ID="your_spaces_key_id"
    export SPACES_SECRET_ACCESS_KEY="your_spaces_secret_key"
    
  5. Registered Domain Name: You need a domain whose DNS can be delegated to DigitalOcean's name servers.

Configuration

Create a terraform.tfvars file:

# Required
domain_name = "fleet.your-domain.com"

# Optional overrides
region = "nyc3"  # Default: nyc3

# Fleet configuration
fleet_config = {
  image_tag          = "fleetdm/fleet:v4.90.0"
  instance_size_slug = "apps-s-1vcpu-1gb"
  instance_count     = 1
  debug_logging      = false
  exec_migration     = true
  # license_key      = "YOUR_FLEET_LICENSE_KEY"  # Optional
  extra_env_vars     = {}
}

# Database configuration
database_config = {
  name          = "fleet-mysql"
  engine        = "mysql"
  version       = "8"
  size          = "db-s-1vcpu-1gb"
  node_count    = 1
  database_name = "fleet"
  database_user = "fleet"
}

# Cache configuration
cache_config = {
  name       = "fleet-cache"
  engine     = "valkey"
  version    = "8"
  size       = "db-s-1vcpu-1gb"
  node_count = 1
}

Deployment Steps

  1. Set environment variables:

    export DIGITALOCEAN_TOKEN="dop_v1_your_token_here"
    export SPACES_ACCESS_KEY_ID="your_spaces_key_id"
    export SPACES_SECRET_ACCESS_KEY="your_spaces_secret_key"
    
  2. Initialize Terraform:

    terraform init
    
  3. Plan the deployment:

    terraform plan -out=tfplan
    
  4. Apply the configuration:

    terraform apply tfplan
    
  5. Delegate DNS: If your domain's DNS is managed elsewhere, update the name servers at your registrar to:

    • ns1.digitalocean.com
    • ns2.digitalocean.com
    • ns3.digitalocean.com

Architecture

graph TD
    subgraph External
        Internet[(Internet)]
        Users[Web Console / fleetd agent]
        GitHub[(GitHub - Vulnerability Resources)]
    end

    subgraph "DigitalOcean"
        subgraph VPC [VPC]
            direction LR
            subgraph PublicFacing [Public Zone]
                AppPlatform[App Platform: Fleet]
            end
            subgraph PrivateZone [Private Zone]
                MySQL[(Managed MySQL)]
                Valkey[(Managed Valkey)]
                Spaces[(Spaces Bucket)]
            end

            AppPlatform --> MySQL
            AppPlatform --> Valkey
            AppPlatform --> Spaces
        end
    end

    Users -- "fleet.yourdomain.com" --> Internet
    Internet -- "fleet.yourdomain.com" --> AppPlatform
    AppPlatform -- "outbound" --> GitHub

Key Differences from AWS/GCP Modules

Feature AWS GCP DigitalOcean
Compute ECS Fargate Cloud Run App Platform
Database Aurora MySQL Cloud SQL MySQL Managed MySQL
Cache ElastiCache Memorystore Managed Valkey
Storage S3 GCS Spaces
Networking VPC + ALB VPC + LB VPC + built-in TLS
DNS Route53 Cloud DNS DigitalOcean DNS
TLS ACM Managed SSL Automatic (Let's Encrypt)

Cost Tiers

Use fleet-10.tfvars or the deployment script:

# Quick deploy with script
../scripts/deploy-digitalocean.sh

# Or manually
terraform plan -var="domain_name=fleet.your-domain.com" -var-file="fleet-10.tfvars"
terraform apply
Resource Size Monthly Cost
App Platform basic-xs (1 GiB) × 1 ~$10
Managed MySQL db-s-1vcpu-1gb × 1 ~$15
Spaces 250 GB included ~$5
VPC, DNS, TLS Free
Total ~$30/month

Full Stack (~$47/month) — Default

For larger deployments or when you need Redis caching:

Resource Size Monthly Cost
App Platform apps-s-1vcpu-1gb × 1 ~$12
Managed MySQL db-s-1vcpu-1gb × 1 ~$15
Managed Valkey db-s-1vcpu-1gb × 1 ~$15
Spaces 250 GB included ~$5
VPC, DNS, TLS Free
Total ~$47/month

Low-Cost (~$32/month)

Managed MySQL without cache:

terraform plan -var="domain_name=fleet.your-domain.com" -var-file="low-cost.tfvars"

Extreme Low-Cost (~$10/month)

Self-hosted MySQL on Droplet, smallest app instance, no cache:

terraform plan -var="domain_name=fleet.your-domain.com" -var-file="extreme-low-cost.tfvars"

⚠️ Warning: Extreme mode runs MySQL on a Droplet without managed backups. Not recommended for production device data.

Cleaning Up

terraform destroy

Important Considerations

  • Spaces Keys: The SPACES_ACCESS_KEY_ID and SPACES_SECRET_ACCESS_KEY environment variables are needed by the Terraform provider to manage Spaces buckets. These are separate from the API token. Generate them at DigitalOcean Spaces Keys.
  • TLS: App Platform automatically provisions and renews Let's Encrypt certificates for custom domains.
  • Database Security: Database firewalls restrict connections to only the App Platform app. No public access is allowed.
  • Migration: The PRE_DEPLOY job runs fleet prepare db before each deployment when exec_migration = true.
  • Scaling: Adjust instance_size_slug and instance_count in fleet_config for horizontal/vertical scaling.