This skill guides writing DigitalOcean infrastructure with OpenTofu/Terraform. Use when provisioning Droplets, VPCs, Managed Databases, Firewalls, or other DO resources.
Provides OpenTofu/Terraform patterns for DigitalOcean infrastructure. Use when provisioning Droplets, VPCs, Managed Databases, Firewalls, or Spaces.
/plugin marketplace add majesticlabs-dev/majestic-marketplace/plugin install majestic-devops@majestic-marketplaceThis skill is limited to using the following tools:
DigitalOcean provides simple, developer-friendly cloud infrastructure. This skill covers OpenTofu/Terraform patterns for DO resources.
terraform {
required_providers {
digitalocean = {
source = "digitalocean/digitalocean"
version = "~> 2.0"
}
}
}
provider "digitalocean" {
# Token from environment: DIGITALOCEAN_TOKEN
}
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}"
}
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]
}
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]
}
| Size | vCPUs | Memory | Use Case |
|---|---|---|---|
s-1vcpu-1gb | 1 | 1 GB | Testing, small apps |
s-1vcpu-2gb | 1 | 2 GB | Small production |
s-2vcpu-4gb | 2 | 4 GB | Medium apps |
s-4vcpu-8gb | 4 | 8 GB | Production workloads |
c-2 | 2 | 4 GB | CPU-intensive |
m-2vcpu-16gb | 2 | 16 GB | Memory-intensive |
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
}
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"]
}
}
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
}
}
}
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
}
| Size | vCPUs | Memory | Storage | Use Case |
|---|---|---|---|---|
db-s-1vcpu-1gb | 1 | 1 GB | 10 GB | Development |
db-s-1vcpu-2gb | 1 | 2 GB | 25 GB | Small production |
db-s-2vcpu-4gb | 2 | 4 GB | 38 GB | Production |
db-s-4vcpu-8gb | 4 | 8 GB | 115 GB | High traffic |
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]
}
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
}
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
}
# 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")
}
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
}
private_uri for database connectionsThis skill should be used when the user asks to "create a slash command", "add a command", "write a custom command", "define command arguments", "use command frontmatter", "organize commands", "create command with file references", "interactive command", "use AskUserQuestion in command", or needs guidance on slash command structure, YAML frontmatter fields, dynamic arguments, bash execution in commands, user interaction patterns, or command development best practices for Claude Code.
This skill should be used when the user asks to "create an agent", "add an agent", "write a subagent", "agent frontmatter", "when to use description", "agent examples", "agent tools", "agent colors", "autonomous agent", or needs guidance on agent structure, system prompts, triggering conditions, or agent development best practices for Claude Code plugins.
This skill should be used when the user asks to "create a hook", "add a PreToolUse/PostToolUse/Stop hook", "validate tool use", "implement prompt-based hooks", "use ${CLAUDE_PLUGIN_ROOT}", "set up event-driven automation", "block dangerous commands", or mentions hook events (PreToolUse, PostToolUse, Stop, SubagentStop, SessionStart, SessionEnd, UserPromptSubmit, PreCompact, Notification). Provides comprehensive guidance for creating and implementing Claude Code plugin hooks with focus on advanced prompt-based hooks API.