Skip to main content

No project description provided

Project description

deployvm

Python CLI for deploying web applications to cloud providers (DigitalOcean, AWS, and Vultr).

When setting up a VM it will:

  • Create a cloud instance (DigitalOcean droplet, AWS EC2, or Vultr VPS)
  • Configure firewall rules to open ports 80, 443, and SSH
  • Create a deploy user with passwordless sudo
  • Set up a swap file
  • Upload your SSH key to the provider
  • Install uv, nginx, supervisord (uv apps) or pm2 (npm apps)
  • Deploy your app and configure it to run as a service
  • Set up nginx as a reverse proxy
  • Optionally provision a Let's Encrypt SSL certificate via certbot

Installation

uv tool install deployvm

Quick Start

1. Configure Provider

Create .env in your project root:

# DigitalOcean (default)
DEPLOY_VM_PROVIDER=digitalocean

# AWS
DEPLOY_VM_PROVIDER=aws
AWS_PROFILE=default
AWS_REGION=ap-southeast-2

# Vultr
DEPLOY_VM_PROVIDER=vultr
VULTR_API_KEY=your-api-key

Auth setup:

  • DigitalOcean: doctl auth init
  • AWS: aws configure
  • Vultr: set VULTR_API_KEY in your environment or .env

2. Deploy Your App

Without SSL (IP-only):

deployvm uv deploy my-server /path/to/app \
    "uv run uvicorn app:app --port 8000" \
    --port 8000

With domain + SSL:

# 1. Get nameservers and set them at your registrar
deployvm nameservers example.com

# 2. Deploy — handles DNS zone setup and waits for propagation automatically
deployvm uv deploy my-server \
    /path/to/app \
    "uv run uvicorn app:app --port 8000" \
    --port 8000 \
    --domain example.com \
    --email you@example.com

Supported app types:

  • uv deploy — any Python app managed by uv + supervisord

    • Requires pyproject.toml and uv for dependency management
    • Command must be a uv run ... invocation (e.g. uvicorn, gunicorn, custom CLI)
  • npm deploy — any npm app managed by PM2

    • Requires package.json with a build script
    • Node.js managed via nvm on the server
    • Builds locally by default (--local-build), uploads build output

3. Manage Your Deployment

deployvm instance verify my-server --domain example.com
deployvm uv logs my-server
deployvm uv restart my-server
deployvm uv sync my-server /path/to/app "uv run uvicorn app:app --port 8000"

Common Workflows

SSL Lockdown Mode

For enhanced security, use --ssl-only to completely block HTTP access at both the firewall and nginx level:

# Deploy with SSL-only mode (blocks port 80 entirely)
deployvm uv deploy my-server /path/to/app \
    "uv run uvicorn app:app --port 8000" \
    --port 8000 --domain example.com --email you@example.com \
    --ssl-only

# Multiple apps with SSL-only mode
deployvm uv deploy my-server /path/to/api \
    "uv run uvicorn app:app --port 8000" \
    --port 8000 --app-name api \
    --domain api.example.com --email you@example.com \
    --ssl-only

deployvm uv deploy my-server /path/to/worker \
    "uv run worker --port 8001" \
    --port 8001 --app-name worker \
    --domain worker.example.com --email you@example.com \
    --ssl-only

SSL-only mode security features:

  • Blocks port 80 at firewall level (UFW + cloud provider security groups)
  • Nginx returns connection drop (444) for any HTTP requests
  • Only HTTPS traffic allowed, no HTTP-to-HTTPS redirect
  • Compatible with multiple apps per instance
  • Certbot still works for SSL certificate provisioning

Add SSL After Deployment

# 1. Deploy without domain first
deployvm uv deploy my-server /path/to/app "uv run uvicorn app:app --port 8000" --port 8000

# 2. Check nameservers and set them at your registrar
deployvm nameservers example.com

# 3. Add SSL — creates DNS zone, sets A records, waits for propagation, runs certbot
deployvm ssl my-server example.com you@example.com

# For a specific app on a multi-app instance, use --app-name or --port
deployvm ssl my-server api.example.com you@example.com --app-name api
deployvm ssl my-server api.example.com you@example.com --port 8000

Multiple Apps on One Instance

Each app on the same instance needs a unique --app-name and a unique --outgoing-port.

With SSL (domain per app):

# First app — creates the instance
deployvm uv deploy my-server /path/to/api \
    "uv run uvicorn app:app --port 8000" \
    --port 8000 --app-name api \
    --domain api.example.com --email you@example.com

# Second app — reuses the existing instance
deployvm uv deploy my-server /path/to/worker \
    "uv run worker --port 8001" \
    --port 8001 --app-name worker \
    --domain worker.example.com --email you@example.com

# Mix Python + npm on the same instance
deployvm npm deploy my-server /path/to/frontend \
    --app-name frontend --port 3000 \
    --domain example.com --email you@example.com

Without SSL (IP + port):

# First app on port 80 (default)
deployvm uv deploy my-server /path/to/api \
    "uv run uvicorn app:app --port 8000" \
    --port 8000 --app-name api

# Second app on port 8080 — internal port must differ from outgoing port
deployvm uv deploy my-server /path/to/app2 \
    "uv run myapp --port 9080" \
    --port 9080 --outgoing-port 8080 --app-name app2

Port rules for --no-ssl apps:

  • --port — internal port the app listens on (127.0.0.1 only)
  • --outgoing-port — external port nginx listens on (default: 80)
  • These must be different: nginx cannot bind to the same port as the app
  • Each app needs a unique --outgoing-port

Configuration

Environment Variables

Variable Description Default
DEPLOY_VM_PROVIDER Cloud provider (aws, digitalocean, or vultr) digitalocean
AWS_PROFILE AWS CLI profile name None
AWS_REGION Default AWS region ap-southeast-2
VULTR_API_KEY Vultr API key None

Application Credentials

Your app's .env inside the app directory is automatically uploaded during deploy or sync.

When deploying to AWS EC2, AWS_PROFILE, AWS_ACCESS_KEY_ID, and AWS_SECRET_ACCESS_KEY are stripped (EC2 uses IAM roles), and AWS_REGION is preserved/added.

Provider Settings

Setting AWS DigitalOcean Vultr
Regions us-east-1, us-west-2, ap-southeast-2 syd1, sgp1, nyc1, sfo3, lon1 syd, sgp, ewr, lax, lhr
VM Sizes t3.micro, t3.small, t3.medium s-1vcpu-1gb, s-2vcpu-2gb, s-4vcpu-8gb vc2-1c-1gb, vc2-1c-2gb, vc2-2c-4gb
DNS Route53 (auto-created) ns1-3.digitalocean.com ns1.vultr.com, ns2.vultr.com
Auth aws configure doctl auth init VULTR_API_KEY env var

AWS Infrastructure Setup

When creating an EC2 instance, the script automatically handles all required AWS infrastructure:

VPC

  • Checks for an existing VPC with subnets, an attached internet gateway, and a route table with a route to the internet gateway
  • Creates a default VPC if none exists in the region

Security group

  • Creates a deploy-vm-web security group (once per region) with:
    • SSH (port 22) restricted to your current public IP
    • HTTP (port 80) open to all
    • HTTPS (port 443) open to all
  • Reuses the existing group on subsequent deploys
  • instance update-ssh-ip updates the SSH rule if your IP changes

SSH key pair

  • Uploads your local SSH public key (~/.ssh/id_ed25519.pub etc.) to EC2 if not already registered

AMI

  • Finds the latest Ubuntu 22.04 LTS AMI from Canonical for your region

IAM role and instance profile (when Bedrock access is needed)

  • Creates an IAM role with EC2 trust policy and AmazonBedrockFullAccess managed policy
  • Creates an EC2 instance profile and attaches the role
  • Waits for IAM propagation before launching the instance

Route53 DNS

  • nameservers creates a hosted zone for your domain if one doesn't exist, then returns the nameservers to configure at your registrar
  • ssl and uv/npm deploy create or upsert A records for domain and www.domain pointing to the instance IP

AWS Bedrock Access

EC2 instances automatically get Bedrock access via IAM roles:

deployvm uv deploy my-server /path/to/app "uv run uvicorn app:app --port 8000" --port 8000
# or with custom role:
deployvm uv deploy my-server /path/to/app "uv run uvicorn app:app --port 8000" --port 8000 --iam-role my-role

Your app code needs no credentials:

import boto3
bedrock = boto3.client('bedrock-runtime', region_name=os.getenv('AWS_REGION'))

Commands Reference

deployvm instance create|delete|list|verify|update-ssh-ip|cleanup
deployvm uv deploy|sync|restart|status|logs
deployvm npm deploy|sync|restart|status|logs
deployvm nameservers <domain>
deployvm ssl <instance> <domain> <email>

ssl is idempotent and handles the full SSL setup sequence: creates the DNS zone and A records, prints nameservers, waits for propagation, installs nginx, and runs certbot. Both IP and domain access work afterwards.

Common options:

  • --provider aws|digitalocean|vultr
  • --region <region>
  • --vm-size <size>
  • --domain <domain> (omit for IP-only access)
  • --email <email> (required with --domain)
  • --app-name <name>
  • --iam-role <name> (AWS only)
  • --ssl-only (block port 80 at firewall level for enhanced security)

Requirements

Tool Purpose Required Install
uv Python package manager Yes curl -LsSf https://astral.sh/uv/install.sh | sh
ssh, rsync, tar, scp File transfer & remote ops Yes Pre-installed (macOS/Linux)
doctl DigitalOcean CLI Optional brew install doctl
aws AWS CLI Optional brew install awscli
vultr-cli Vultr CLI Optional brew install vultr-cli
npm npm app local builds Optional brew install node

SSH Key

Automatically uploads ~/.ssh/id_ed25519.pub, id_rsa.pub, or id_ecdsa.pub to the provider on first use.

Instance State

Metadata stored in <name>.instance.json:

{
  "id": "i-0abc123",
  "ip": "54.123.45.67",
  "provider": "aws",
  "region": "ap-southeast-2",
  "vm_size": "t3.small",
  "user": "deploy",
  "apps": [
    {"name": "api", "type": "uv", "port": 8000},
    {"name": "frontend", "type": "npm", "port": 3000}
  ]
}

Running Integration Tests

Integration tests spin up a real cloud instance, run the full deployment lifecycle, then delete it.

# Run all integration tests (Vultr, Sydney region by default)
pytest tests/ -m integration --provider vultr -s -v

# Run a single test
pytest tests/test_integration.py::test_01_create -m integration -s

Prerequisites:

  • VULTR_API_KEY set in environment or .env
  • vultr-cli installed (brew install vultr-cli)
  • SSH key at ~/.ssh/id_rsa (no passphrase)

The fixture automatically retries up to 5 times if a newly created instance gets an unreachable IP — common with Vultr Sydney due to IP range variability.

Support

  • Issues: GitHub Issues
  • Help: deployvm --help or deployvm <command> --help

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

deployvm-0.1.16.tar.gz (905.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

deployvm-0.1.16-py3-none-any.whl (57.7 kB view details)

Uploaded Python 3

File details

Details for the file deployvm-0.1.16.tar.gz.

File metadata

  • Download URL: deployvm-0.1.16.tar.gz
  • Upload date:
  • Size: 905.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for deployvm-0.1.16.tar.gz
Algorithm Hash digest
SHA256 b012de67e7df5c7feb6cec00dcebe39cd4f903ae81d4ee6187302bd83f2e5f51
MD5 9eb045c9eb8b70ad657922e0f280821a
BLAKE2b-256 c31db538b714b52fc09c717b051d90bde54fcc24e08d3405cbd3273307cbea92

See more details on using hashes here.

File details

Details for the file deployvm-0.1.16-py3-none-any.whl.

File metadata

  • Download URL: deployvm-0.1.16-py3-none-any.whl
  • Upload date:
  • Size: 57.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: uv/0.11.7 {"installer":{"name":"uv","version":"0.11.7","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"macOS","version":null,"id":null,"libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":null}

File hashes

Hashes for deployvm-0.1.16-py3-none-any.whl
Algorithm Hash digest
SHA256 2491dc0d4fd701f051a7132f3aa8c0ebc5a2c621626aa076a6cce1b055f0d771
MD5 960c4d3099146b7e9a57795852aa74ae
BLAKE2b-256 85ae4b3e98f8b308b1636f09dc734f7caf1b63a8cfc0a01c411a5e38e4763644

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page