CLI utility to create and manage VMs
Initially supports only DigitalOcean using the python-digitalocean module.
This project uses uv for dependency management and builds.
Install uv:
curl -LsSf https://astral.sh/uv/install.sh | sh
uv sync
This creates a .venv virtual environment and installs all dependencies (including dev tools like flake8 and black).
Run the CLI during development:
uv run machine --help
Run the linter:
uv run flake8
Build a self-contained executable using shiv:
./sh/build-package.sh
This produces build/machine, a single-file Python zipapp.
Install directly from the GitHub repository using uv:
uv tool install git+https://github.com/stirlingbridge/machine.git
Alternatively, download the machine binary from the releases page, make it executable, and place it on your PATH:
chmod +x machine
sudo mv machine /usr/local/bin/
Access token and other settings configured in the file ~/.machine/config.yml :
digital-ocean:
access-token: dop_v1_xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
ssh-key: my-ssh-key-name
dns-zone: example.com
machine-size: s-4vcpu-8gb
image: ubuntu-22-04-x64
region: nyc3
project: Infrastructure
machines:
example:
new-user-name: alice
script-dir: /opt/setup-scripts
script-url: https://raw.githubusercontent.com/example/setup-machine.sh
script-path: /opt/setup-scripts/setup-machine.sh
script-args: "-y"digital-ocean section:
| Key | Required | Description |
|---|---|---|
access-token |
Yes | DigitalOcean API access token |
ssh-key |
Yes | Name of the SSH key in your DigitalOcean account to use for new machines |
dns-zone |
No | DNS zone for automatic DNS record creation/deletion |
machine-size |
Yes | Default machine size slug (e.g. s-4vcpu-8gb) |
image |
Yes | Default image name (e.g. ubuntu-22-04-x64) |
region |
Yes | Default region code (e.g. nyc3) |
project |
No | DigitalOcean project name to assign new machines to |
machines section:
Each entry under machines: defines a machine type that can be referenced with create --type:
| Key | Required | Description |
|---|---|---|
new-user-name |
Yes | Username for the non-root user created on the machine |
script-url |
No | URL to download an initialization script from |
script-dir |
No | Directory to store the initialization script |
script-path |
No | Full path for the initialization script |
script-args |
No | Arguments passed to the initialization script (supports variable expansion) |
If script-url, script-dir, and script-path are all provided, the script is downloaded and executed as the new user during cloud-init. The following variables are available for expansion in script-args:
$MACHINE_SCRIPT_URL— URL of the initialization script$MACHINE_SCRIPT_DIR— directory path for the script$MACHINE_FQDN— fully qualified domain name of the machine (if DNS is configured)
Examples of advanced machine setup scripts can be found in the machine-provisioning repository.
Each invocation of machine uses a session ID (auto-generated and stored in ~/.machine/session-id.yml). Machines are tagged with their session ID on creation. By default, list, status, list-domain, and destroy only operate on machines from the current session. Use the --all flag to include machines from other sessions or machines not created by this tool.
The session ID can be overridden with the global --session-id option.
Machines created by this tool are automatically tagged with:
machine:created— identifies the machine as created by this toolmachine:type:<type-name>— the machine type from the configmachine:session:<session-id>— the session that created the machine
$ machine --help
Usage: machine [OPTIONS] COMMAND [ARGS]...
Options:
--debug Enable debug output
--quiet Suppress all non-essential output
--verbose Enable verbose output
--dry-run Run but do not do anything
--config-file <PATH> Specify the config file (default
~/.machine/config.yml)
--session-id <ID> Override the default session ID
-h, --help Show this message and exit.
Commands:
create Create a machine
destroy Destroy one or more machines
domains List dns domains
list List machines
list-domain List domain records
projects List projects
ssh-keys List ssh keys
status Machine status
types List configured machine types
version Display version
Create a new machine on DigitalOcean. By default, the machine is initialized with cloud-init (using the specified --type from config) and a DNS A record is created.
$ machine create --help
Usage: machine create [OPTIONS]
Create a machine
Options:
-n, --name <MACHINE-NAME> Name for new machine [required]
-t, --tag <TAG-TEXT> Tag to be applied to new machine
-m, --type <MACHINE-TYPE> Machine type from config (required if --initialize)
-r, --region <REGION-CODE> Region (overrides config default)
-s, --machine-size <MACHINE-SLUG>
Machine size (overrides config default)
-s, --image <IMAGE-NAME> Image (overrides config default)
--wait-for-ip / --no-wait-for-ip Wait for IP address assignment (default: off)
--update-dns / --no-update-dns Create DNS A record (default: on)
--initialize / --no-initialize Initialize with cloud-init (default: on)
-h, --help Show this message and exit.
Supported regions: NYC1, NYC3, AMS3, SFO2, SFO3, SGP1, LON1, FRA1, TOR1, BLR1, SYD1
When --update-dns is enabled (the default), the command waits for the droplet's IP address and creates an A record in the configured dns-zone with a 5-minute TTL.
When --initialize is enabled (the default), a cloud-config user-data payload is generated that creates a non-root user with sudo access, installs the SSH key, and optionally downloads and runs an initialization script.
If a project is configured, the machine is automatically assigned to that DigitalOcean project.
Destroy one or more machines by droplet ID. By default, requires confirmation and deletes associated DNS records.
$ machine destroy --help
Usage: machine destroy [OPTIONS] [DROPLET-IDS]...
Destroy one or more machines
Options:
--confirm / --no-confirm Require confirmation (default: on)
--delete-dns / --no-delete-dns Delete associated DNS records (default: on)
--all Include machines not created by this tool
or by other sessions
-h, --help Show this message and exit.
Confirmation requires typing exactly YES (not "y", "yes", or "Yes"). Use --no-confirm to skip.
Safety checks prevent destroying machines that were not created by this tool or that belong to a different session, unless --all is specified.
List machines with optional filtering.
$ machine list --help
Usage: machine list [OPTIONS]
List machines
Options:
--id <MACHINE-ID> Filter by id
-n, --name <MACHINE-NAME> Filter by name
-t, --tag <TAG-TEXT> Filter by tag
-m, --type <MACHINE-TYPE> Filter by type
-r, --region <REGION> Filter by region
-o, --output <FORMAT> Output format (json)
-q, --quiet Only display machine IDs
--unique Return an error if more than one match
--all Include all machines from all sessions
-h, --help Show this message and exit.
Output formats:
- Default:
name (id, region, type): ip_address --quiet: droplet IDs only--output json: JSON array with id, name, tags, region, ip, type
Check the status of machines, including querying a custom status endpoint.
$ machine status --help
Usage: machine status [OPTIONS]
Machine status
Options:
--id <MACHINE-ID> Filter by id
-n, --name <MACHINE-NAME> Filter by name
-t, --tag <TAG-TEXT> Filter by tag
-m, --type <MACHINE-TYPE> Filter by type
-r, --region <REGION> Filter by region
-o, --output <FORMAT> Output format (json)
--status-check <CHECK> Status check to perform (default: cloud-init-status)
-q, --quiet Only display machine IDs
--all Include all machines from all sessions
-h, --help Show this message and exit.
In addition to the DigitalOcean droplet status, this command queries each machine at http://<ip>:4242/cgi-bin/<status-check> (default: cloud-init-status) for custom status information. If the endpoint is unreachable, the status is reported as UNKNOWN.
List DNS records within a domain zone.
$ machine list-domain --help
Usage: machine list-domain [OPTIONS] [ZONE]
List domain records
Options:
-n, --name <RECORD-NAME> Filter by record name
-m, --type <RECORD-TYPE> Filter by record type (default: A and AAAA, use * for all)
-o, --output <FORMAT> Output format (json)
-q, --quiet Only display record names
--all Include all records from all sessions
-h, --help Show this message and exit.
If ZONE is omitted, uses the dns-zone from config. By default, only shows A and AAAA records associated with machines from the current session.
Output formats:
- Default:
name\ttype\tdata --quiet: record names only--output json: JSON array with id, droplet info, name, fqdn, zone, data, ttl, type
List all DNS domains in your DigitalOcean account. Takes no options.
List SSH keys in your DigitalOcean account. Output format: id: name (fingerprint)
List DigitalOcean project names. Takes no options.
List all machine types defined in the config file (from the machines section). Takes no options.