Marketplace

digitalocean-coder

This skill guides writing DigitalOcean infrastructure with OpenTofu/Terraform. Use when provisioning Droplets, VPCs, Managed Databases, Firewalls, or other DO resources.

allowed_tools: Read, Write, Edit, Grep, Glob, Bash

$ Installer

git clone https://github.com/majesticlabs-dev/majestic-marketplace /tmp/majestic-marketplace && cp -r /tmp/majestic-marketplace/plugins/majestic-devops/skills/digitalocean-coder ~/.claude/skills/majestic-marketplace

// tip: Run this command in your terminal to install the skill


name: digitalocean-coder description: This skill guides writing DigitalOcean infrastructure with OpenTofu/Terraform. Use when provisioning Droplets, VPCs, Managed Databases, Firewalls, or other DO resources. allowed-tools: Read, Write, Edit, Grep, Glob, Bash

DigitalOcean Coder

Overview

DigitalOcean provides simple, developer-friendly cloud infrastructure. This skill covers OpenTofu/Terraform patterns for DO resources.

Provider Setup

terraform {
  required_providers {
    digitalocean = {
      source  = "digitalocean/digitalocean"
      version = "~> 2.0"
    }
  }
}

provider "digitalocean" {
  # Token from environment: DIGITALOCEAN_TOKEN
}

VPC (Virtual Private Cloud)

resource "digitalocean_vpc" "main" {
  name     = "${var.project}-${var.environment}-vpc"
  region   = var.region
  ip_range = "10.10.0.0/16"

  description = "VPC for ${var.project} ${var.environment}"
}

Droplets (Compute)

Basic Droplet

resource "digitalocean_droplet" "app" {
  name     = "${var.project}-${var.environment}-app"
  region   = var.region
  size     = var.droplet_size  # s-1vcpu-1gb, s-2vcpu-4gb, etc.
  image    = "ubuntu-22-04-x64"
  vpc_uuid = digitalocean_vpc.main.id

  ssh_keys   = var.ssh_key_ids
  monitoring = true
  ipv6       = false

  tags = [var.project, var.environment]
}

Droplet with Cloud-Init

resource "digitalocean_droplet" "app" {
  name     = "${var.project}-app"
  region   = var.region
  size     = "s-1vcpu-2gb"
  image    = "ubuntu-22-04-x64"
  vpc_uuid = digitalocean_vpc.main.id

  ssh_keys   = var.ssh_key_ids
  monitoring = true

  user_data = <<-EOT
    #cloud-config
    package_update: true
    packages:
      - docker.io
      - docker-compose-plugin
    users:
      - name: deploy
        groups: docker
        sudo: ALL=(ALL) NOPASSWD:ALL
        shell: /bin/bash
        ssh_authorized_keys:
          - ${var.deploy_ssh_key}
    runcmd:
      - systemctl enable --now docker
      - sed -i 's/PermitRootLogin yes/PermitRootLogin no/' /etc/ssh/sshd_config
      - systemctl restart sshd
  EOT

  tags = [var.project]
}

Common Droplet Sizes

SizevCPUsMemoryUse Case
s-1vcpu-1gb11 GBTesting, small apps
s-1vcpu-2gb12 GBSmall production
s-2vcpu-4gb24 GBMedium apps
s-4vcpu-8gb48 GBProduction workloads
c-224 GBCPU-intensive
m-2vcpu-16gb216 GBMemory-intensive

Reserved IP (Static IP)

resource "digitalocean_reserved_ip" "app" {
  region = var.region
}

resource "digitalocean_reserved_ip_assignment" "app" {
  ip_address = digitalocean_reserved_ip.app.ip_address
  droplet_id = digitalocean_droplet.app.id
}

output "app_ip" {
  value = digitalocean_reserved_ip.app.ip_address
}

Firewall

Basic Web Server Firewall

resource "digitalocean_firewall" "web" {
  name = "${var.project}-web-firewall"

  droplet_ids = [digitalocean_droplet.app.id]

  # Inbound rules
  inbound_rule {
    protocol         = "tcp"
    port_range       = "22"
    source_addresses = var.ssh_allowed_ips  # Restrict SSH access
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "80"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "443"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  # Outbound rules
  outbound_rule {
    protocol              = "tcp"
    port_range            = "all"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "udp"
    port_range            = "all"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "icmp"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }
}

Dynamic IP Whitelist

variable "db_allowed_ips" {
  type        = list(string)
  default     = []
  description = "IPs allowed to access database directly"
}

resource "digitalocean_database_firewall" "postgres" {
  cluster_id = digitalocean_database_cluster.postgres.id

  # Always allow app droplet
  rule {
    type  = "droplet"
    value = digitalocean_droplet.app.id
  }

  # Dynamic IP whitelist for developers
  dynamic "rule" {
    for_each = var.db_allowed_ips
    content {
      type  = "ip_addr"
      value = rule.value
    }
  }
}

Managed Database

PostgreSQL Cluster

resource "digitalocean_database_cluster" "postgres" {
  name       = "${var.project}-${var.environment}-pg"
  engine     = "pg"
  version    = "16"
  size       = var.db_size  # db-s-1vcpu-1gb, db-s-2vcpu-4gb
  region     = var.region
  node_count = 1  # Increase for HA

  # CRITICAL: Use private network
  private_network_uuid = digitalocean_vpc.main.id

  tags = [var.project, var.environment]
}

# Database firewall - restrict access
resource "digitalocean_database_firewall" "postgres" {
  cluster_id = digitalocean_database_cluster.postgres.id

  rule {
    type  = "droplet"
    value = digitalocean_droplet.app.id
  }
}

output "database_uri" {
  value     = digitalocean_database_cluster.postgres.uri
  sensitive = true
}

output "database_private_uri" {
  value     = digitalocean_database_cluster.postgres.private_uri
  sensitive = true
}

Database Sizes

SizevCPUsMemoryStorageUse Case
db-s-1vcpu-1gb11 GB10 GBDevelopment
db-s-1vcpu-2gb12 GB25 GBSmall production
db-s-2vcpu-4gb24 GB38 GBProduction
db-s-4vcpu-8gb48 GB115 GBHigh traffic

Redis Cluster

resource "digitalocean_database_cluster" "redis" {
  name       = "${var.project}-${var.environment}-redis"
  engine     = "redis"
  version    = "7"
  size       = "db-s-1vcpu-1gb"
  region     = var.region
  node_count = 1

  private_network_uuid = digitalocean_vpc.main.id
  tags                 = [var.project]
}

Spaces (Object Storage)

resource "digitalocean_spaces_bucket" "assets" {
  name   = "${var.project}-assets"
  region = var.spaces_region  # nyc3, sfo3, ams3, sgp1, fra1

  acl = "private"  # or "public-read"
}

resource "digitalocean_spaces_bucket_cors_configuration" "assets" {
  bucket = digitalocean_spaces_bucket.assets.id
  region = var.spaces_region

  cors_rule {
    allowed_headers = ["*"]
    allowed_methods = ["GET", "PUT", "POST"]
    allowed_origins = ["https://${var.domain}"]
    max_age_seconds = 3600
  }
}

output "spaces_endpoint" {
  value = digitalocean_spaces_bucket.assets.bucket_domain_name
}

DNS Records

resource "digitalocean_domain" "main" {
  name = var.domain
}

resource "digitalocean_record" "app" {
  domain = digitalocean_domain.main.id
  type   = "A"
  name   = "@"
  value  = digitalocean_reserved_ip.app.ip_address
  ttl    = 300
}

resource "digitalocean_record" "www" {
  domain = digitalocean_domain.main.id
  type   = "CNAME"
  name   = "www"
  value  = "@"
  ttl    = 300
}

SSH Keys

# Reference existing key
data "digitalocean_ssh_key" "deploy" {
  name = "deploy-key"
}

# Or create new key
resource "digitalocean_ssh_key" "deploy" {
  name       = "${var.project}-deploy"
  public_key = file("~/.ssh/deploy.pub")
}

Complete Production Stack

locals {
  name_prefix = "${var.project}-${var.environment}"
}

# VPC
resource "digitalocean_vpc" "main" {
  name     = "${local.name_prefix}-vpc"
  region   = var.region
  ip_range = "10.10.0.0/16"
}

# App Server
resource "digitalocean_droplet" "app" {
  name     = "${local.name_prefix}-app"
  region   = var.region
  size     = var.droplet_size
  image    = "ubuntu-22-04-x64"
  vpc_uuid = digitalocean_vpc.main.id

  ssh_keys   = [data.digitalocean_ssh_key.deploy.id]
  monitoring = true

  user_data = file("${path.module}/cloud-init.yaml")
  tags      = [var.project, var.environment]
}

# Static IP
resource "digitalocean_reserved_ip" "app" {
  region = var.region
}

resource "digitalocean_reserved_ip_assignment" "app" {
  ip_address = digitalocean_reserved_ip.app.ip_address
  droplet_id = digitalocean_droplet.app.id
}

# Firewall
resource "digitalocean_firewall" "app" {
  name        = "${local.name_prefix}-firewall"
  droplet_ids = [digitalocean_droplet.app.id]

  inbound_rule {
    protocol         = "tcp"
    port_range       = "22"
    source_addresses = var.ssh_allowed_ips
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "80"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  inbound_rule {
    protocol         = "tcp"
    port_range       = "443"
    source_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "tcp"
    port_range            = "all"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }

  outbound_rule {
    protocol              = "udp"
    port_range            = "all"
    destination_addresses = ["0.0.0.0/0", "::/0"]
  }
}

# Database
resource "digitalocean_database_cluster" "postgres" {
  name                 = "${local.name_prefix}-pg"
  engine               = "pg"
  version              = "16"
  size                 = var.db_size
  region               = var.region
  node_count           = 1
  private_network_uuid = digitalocean_vpc.main.id
  tags                 = [var.project]
}

resource "digitalocean_database_firewall" "postgres" {
  cluster_id = digitalocean_database_cluster.postgres.id

  rule {
    type  = "droplet"
    value = digitalocean_droplet.app.id
  }
}

# Outputs
output "app_ip" {
  value = digitalocean_reserved_ip.app.ip_address
}

output "database_uri" {
  value     = digitalocean_database_cluster.postgres.private_uri
  sensitive = true
}

Best Practices

Security

  • Always use VPC for private networking
  • Restrict database to droplet access only
  • Use reserved IPs for consistent addressing
  • Limit SSH to specific IPs via firewall
  • Use cloud-init to disable root login

Cost Optimization

  • Use appropriate droplet sizes
  • Single-node databases for non-critical workloads
  • Reserved IPs are free when assigned
  • Monitor with built-in metrics

Networking

  • Use private_uri for database connections
  • Place all resources in same VPC
  • Use firewall rules, not iptables
  • Enable monitoring on droplets

Repository

majesticlabs-dev
majesticlabs-dev
Author
majesticlabs-dev/majestic-marketplace/plugins/majestic-devops/skills/digitalocean-coder
13
Stars
0
Forks
Updated2d ago
Added5d ago