Overview
do-manager is built directly on godo,
the official DigitalOcean Go client. Two interfaces, one codebase: a CLI you run at the terminal
and a library you import into any Go program.
It covers the full DO surface: Droplets, DNS, SSH keys, firewalls, VPCs, reserved IPs, snapshots. On top of that it adds higher-level ops: campaign orchestration (deploy an entire stack in one command, rotate IPs when they get burned, rebuild a node, destroy everything cleanly), opinionated firewall presets, a security audit scanner, and cloud-init templates for common tools — Havoc, Sliver, GoPhish, evilginx2, nginx redirectors, WireGuard.
Use it as a CLI for day-to-day infra work. Use it as a library when you want to provision
Droplets or manage firewalls from Go code without shelling out to doctl.
Use it for Red Team campaigns where infrastructure lifecycle matters.
-o json.pkg/droplet, pkg/firewall, pkg/vpc, pkg/tmpl. Typed API, no binary dependency.Why do-manager?
Three approaches are common for managing DO infra from a Red Team perspective.
Here is where each one breaks down and where do-manager fills the gap.
| doctl | bash + curl API | Terraform / Pulumi | do-manager | |
|---|---|---|---|---|
| Type | Official DO CLI | Ad-hoc scripts | Infrastructure-as-Code | CLI + Go library |
| Campaign orchestration | No | Manual | Partial | Yes (deploy/status/destroy/rotate) |
| Firewall presets | No | No | No | c2, phishing, redirector, bastion, lockdown |
| IP rotation | No | Script it yourself | No | campaign rotate-ips |
| Parallel batch ops | No | background & wait | Partial | Yes (goroutines, per-slot errors) |
| Importable as Go lib | No | No | No (SDK different package) | import "pkg/droplet" |
| Security audit | No | No | No | Yes (CRITICAL/HIGH exit 1) |
| Requires state file | No | No | Yes (.tfstate) | No (stateless) |
| Requires external binary | Yes (doctl) | curl, jq | Yes (terraform) | No |
doctl is fine for one-off manual ops at the terminal. It covers every DO resource but has no Red Team abstractions and no campaign concept.
bash + curl works and most Red Teamers start here. It breaks down fast: race conditions in parallel provisioning, fragile ID extraction with
grep/jq,
no error aggregation, scripts that differ between operators.Terraform is the right choice for long-lived stable infrastructure. It is overkill for engagements: statefile management is a liability, the feedback loop is slow, and there are no Red Team primitives (no firewall presets, no IP rotation, no node rebuild).
do-manager is the choice when you need to provision a full engagement stack fast, rotate IPs when they get burned, tear everything down cleanly, and optionally embed all of that into a Go automation program.
Installation
Requires Go 1.23+.
Releases are built automatically by GoReleaser on every v* tag push.
Checksums are provided in checksums.txt.
Configuration
The API token is resolved in this priority order — first match wins:
| Method | Example |
|---|---|
--token flag | do-manager --token dop_v1_xxx droplet list |
DO_TOKEN env var | export DO_TOKEN=dop_v1_xxx |
~/.do-manager.yaml | token: dop_v1_xxx |
CLI Reference
Global flags available on every command:
| Flag | Description |
|---|---|
--token string | DigitalOcean API token (overrides DO_TOKEN) |
-o, --output string | Output format: table (default) or json |
Droplets
List
Aliases: d ls droplets list
Get
Create
Create flags
| Flag | Default | Description |
|---|---|---|
-n, --name required | Droplet name (base name for batch) | |
-c, --count | 1 | Number of Droplets to provision in parallel |
-r, --region | nyc1 | Region slug |
-s, --size | s-1vcpu-1gb | Size slug |
-i, --image | ubuntu-22-04-x64 | Image slug |
--ssh-keys | SSH key IDs (comma-separated) | |
--tags | Tags (comma-separated) | |
--user-data | Cloud-init script content | |
--ipv6 | false | Enable IPv6 |
--backups | false | Enable automatic backups |
-w, --wait | false | Poll until active, then print IP(s) |
Delete
Power actions
SSH Keys
Regions
Sizes
Images
JSON Output
Every command supports -o json (or --output json).
The flag is global — attach it anywhere in the command line.
--wait) are redirected to stderr in JSON mode,
keeping stdout clean and parseable.
Shell Completion
do-manager completion <shell> generates an autocompletion script.
Supported shells: bash, zsh, fish, powershell.
Version
Version, commit, and build date are injected at build time via -ldflags.
Release binaries carry the correct Git tag automatically via GoReleaser.
DNS
Manage domains and DNS records directly from the CLI. Supports A, AAAA, CNAME, MX, TXT, NS, SRV, CAA.
Domain commands
Record commands
Firewalls
Create and manage Cloud Firewalls. Rules use the format proto:ports:addresses.
Firewall Presets
Apply a named opinionated ruleset for common Red Team roles. Use --operator-ip to restrict SSH to your IP only.
| Profile | Inbound | Outbound | Use case |
|---|---|---|---|
c2 | 443, 80, 53, SSH(op-ip) | all | Command & Control server |
phishing | 443, 80, 8080, SSH(op-ip) | all | GoPhish / evilginx2 |
redirector | 443, 80, SSH(op-ip) | 443, 80 | Traffic redirector (socat/nginx) |
bastion | SSH(op-ip) only | all | Jump host |
lockdown | SSH(op-ip) only | none | Fully locked node |
Reserved IPs
Reserved IPs are static addresses that persist even when a Droplet is deleted or rebuilt. Use them for DNS stability - point your domain to the reserved IP, swap the underlying Droplet freely.
Snapshots
Create, list, and delete Droplet snapshots.
Snapshots are the foundation for repeatable campaign deployments.
The workflow: provision one Droplet, install and configure your tooling
(GoPhish, Havoc C2, evilginx2, nginx redirector config, WireGuard), take a snapshot,
delete the source Droplet. On engagement day, campaign deploy --snapshot <slug>
restores that exact state across N nodes in parallel. No reinstalling,
no drift between nodes.
VPC
A VPC (Virtual Private Cloud) is an isolated Layer-2 private network scoped to a region. Every Droplet inside gets a private IP in addition to its public one. Traffic between VPC members stays on the DO fabric - it never leaves the datacenter.
What VPC is and is not. VPC gives you private network reachability between Droplets in the same region. It is not the channel you use to proxy C2 traffic - that is handled at the application layer (SSH tunnel, WireGuard, socat, nginx). VPC is the network isolation layer: your C2 node has no public firewall rule for the C2 port, so the only path to it is via the private IP, which only nodes inside the same VPC can reach.
Redirector-to-C2 connection options
Three common approaches to route traffic from redirector to C2. All are compatible with VPC:
| Method | How it works | Tradeoffs |
|---|---|---|
| DO VPC private IP | Both nodes in same region/VPC. nginx/socat on redirector forwards to 10.x.x.x:port. No public port on C2. |
DO-only. Requires same region. Zero extra setup once nodes are up. |
| WireGuard tunnel | Peer-to-peer VPN between redirector and C2. C2 listens only on wg0 interface. Works cross-provider, cross-region. |
Extra setup in user-data/cloud-init. More opsec: no public IP on C2 at all. |
| SSH reverse tunnel | C2 initiates: ssh -R 8080:localhost:8080 user@redirector. Redirector forwards inbound to the tunnel. |
Simple. C2 needs no inbound port. Tunnel drops if SSH dies - use autossh/systemd. |
VPC architecture
Commands
Campaign
Deploy and destroy a complete infrastructure campaign with a single command.
A campaign groups Droplets, a Firewall, and Reserved IPs under a tag (campaign:<name>).
All resources are created in the correct order and torn down atomically.
The campaign abstraction exists because Red Team infra has a lifecycle: spin up fast before the engagement,
rotate IPs when they get burned, rebuild a single compromised node without touching the rest,
tear everything down cleanly at the end. Doing this with doctl one command at a time
is possible but tedious and error-prone under pressure. campaign deploy and
campaign destroy are atomic - either the whole stack is up, or you know exactly
which slot failed.
campaign:<name>
so status and destroy can find every resource by tag.
Multi-role campaigns additionally create per-role firewalls and per-role Droplet tags (role:c2, role:redirector).
Full workflow
Key flags for campaign deploy
| Flag | Description |
|---|---|
--name required | Campaign name - used as tag prefix and resource names |
--count | Number of Droplets to provision in parallel (default 1) |
--snapshot | Image slug or snapshot slug/ID to deploy from |
--vpc-uuid | Place all Droplets inside this VPC |
--inbound | Firewall inbound rule: proto:ports:addresses (repeatable) |
--outbound | Firewall outbound rule (repeatable) |
--reserve-ips | Reserve and assign a static IP to each Droplet |
--user-data-file | Bash / cloud-init script run at first boot |
--ssh-keys | SSH key IDs to embed (comma-separated) |
--role | Multi-role spec: name:count=N:snapshot=slug:preset=c2:reserve-ips (repeatable, replaces simple flags) |
--operator-ip | Restrict SSH in preset firewall rules to this IP only |
--vpc-auto | Auto-create a VPC and place all Droplets inside it |
Multi-role campaign
Deploy multiple distinct node types in a single command using --role.
Each role gets its own Droplets, its own firewall (preset or custom), and optional reserved IPs.
All roles share the campaign tag for unified status / destroy.
name:count=N:snapshot=slug:size=s-1vcpu-1gb:preset=c2:reserve-ipsEvery field except
name is optional and falls back to campaign-level flags.
reserve-ips is a boolean token (no value needed).
| Role token | Description |
|---|---|
name (first token) | Role label, used in resource names and tags: role:c2, campaign-c2-fw |
count=N | Number of Droplets for this role (default 1) |
snapshot=slug | Image slug or snapshot name (falls back to --snapshot) |
image=slug | Alias for snapshot |
size=slug | Droplet size (falls back to --size) |
preset=name | Firewall preset: c2, phishing, redirector, bastion, lockdown |
reserve-ips | Reserve and assign a static IP per Droplet in this role |
Rebuild a burned node
When a single node is compromised, rebuild it without touching the rest of the campaign. The reserved IP stays assigned.
Templates
Built-in cloud-init bash scripts embedded directly in the binary. Each script installs and configures a specific tool (C2, redirector, VPN, phishing) and is ready to run at Droplet first boot via DigitalOcean's cloud-init mechanism.
Variable substitution uses Go's text/template ({{.VarName}} syntax).
Every variable has a declared default in the script header — unset variables silently
fall back to their defaults. Use --template-var KEY=VALUE to override.
Available templates
| Name | Description | Key variables |
|---|---|---|
c2-havoc |
Havoc C2 framework - build from source, teamserver as systemd service | C2Port C2Host TeamserverPassword |
c2-sliver |
Sliver C2 - download latest release, generate operator config, systemd | C2Port OperatorName |
redirector-nginx |
nginx HTTPS reverse proxy with URI path filtering. certbot or self-signed TLS. | C2BackendHost Domain C2URIPath |
redirector-apache |
Apache2 mod_rewrite with User-Agent + URI filtering for C2 profile matching | C2BackendHost C2UserAgent C2URIPath |
wireguard-server |
WireGuard VPN server — generates server keypair, configures wg0, enables IP forwarding | WGListenPort WGServerNet |
wireguard-client |
WireGuard peer — connects to a WireGuard server, prints client pubkey for server registration | WGServerEndpoint WGServerPublicKey WGClientNet |
gophish |
GoPhish phishing framework — latest release, admin panel bound to 127.0.0.1 only | AdminListenPort PhishListenPort |
evilginx2 |
evilginx2 reverse proxy phishing — latest release, auto-detects public IP if not set | Domain ExternalIP RedirectURL |
hardening |
SSH hardening, fail2ban, non-root operator user creation with SSH key injection | SSHPort OperatorUser OperatorPubKey |
Commands
Deploy a single node with a template
Full campaign with templates per role
WireGuard requires a two-step key exchange. Deploy the C2 with
template=wireguard-server first. The cloud-init script prints the server public key
in the boot logs (journalctl -u cloud-init). Then deploy the redirector with
template=wireguard-client and pass the server public key via
--template-var WGServerPublicKey=<key>.
Audit
Scan your account for security misconfigurations and hygiene issues.
Exits with code 1 on any CRITICAL or HIGH finding - integrates cleanly into CI/CD pipelines and monitoring scripts.
What it checks
| Severity | Check |
|---|---|
| CRITICAL | Droplets with no firewall attached (fully exposed) |
| HIGH | Firewall rule exposes port 22/3389/5900 to 0.0.0.0/0 |
| MEDIUM | Reserved IP not assigned to any Droplet (idle billing) |
| INFO | Snapshot older than --max-snapshot-age days (default 90) |
Library Usage
The pkg/ packages are designed to be imported in other Go projects.
Each package is stateless and accepts a context.Context on every call.