doctl for command line deployment of digitalocean droplet

Introduction

In this post you will find my notes on deploying a digitalocean droplet, the name digitalocean gives to a virtual private server (VPS) instance, from the command line. For context, this website runs on a digitalocen droplet, running nginx. My goal is to use doctl, the command line tool provided by digitalocean, to make this process quick and reproducible.

The reference for this post is located in the digitalocean docs: Set up a Production-Ready Droplet . You also have to choose the Using the doctl CLI tab in the "Before You Start" section to follow along. This post documents my process following the documentation. My goal is to document my results here, while trying things out and making adjustments.

warning

A reminder that launching a droplet on digitalocean is NOT free. So, proceed with caution and make sure you have the funds available to move forward :)

Prerequisites

Install doctl

The first step is to install doctl following the instructions here . I used the "GitHub Download (Linux, macOS)" instructions, as detailed below.

First up, get the gzipped archive (check for the latest version at the link above):

$ cd ~
$ wget https://github.com/digitalocean/doctl/releases/download/v1.120.1/doctl-1.120.1-linux-amd64.tar.gz

Next, extract the binary and move it to /usr/local/bin/

$ tar xf ~/doctl-1.120.1-linux-amd64.tar.gz
$ sudo mv ~/doctl /usr/local/bin

Authenticate doctl and set CONTEXT

In order to authenticate doctl you need to create a token at the digitalocean website, as detailed here . Of course, this means that you need a digitalocean account to continue. Be sure to record the token in a safe place! Finally, you need to authenticate and give the context a name. In this example I'll use my_account (you will be prompted for the token here):

$ doctl auth init --context my_account

and switch to the created context using

$ doctl auth switch --context my_account

Validate and check account

Everything should be set to go at this point. A final check will pull your digitalocean account details as a check:

$ doctl account get

SSH keys

An ssh key needs to be added to digitalocean for secure access to created resources, like the droplet we want to spin up. There are two ways to do this:

I will create a new key, just for digitalocean testing:

$ ssh-keygen -f ~/.ssh/do_test

This will prompt for a passphrase. If you want no passphrase, you can hit enter twice. Of course, if you choose a passphrase store it carefully for the next time you use this ssh key. We can check that the key file was created using

$ ls -la ~/.ssh/do_*
-rw------- 1 username username 2610 Feb 19 15:02 /home/username/.ssh/do_test
-rw-r--r-- 1 username username 575 Feb 19 15:02 /home/username/.ssh/do_test.pub

Next, we have to import this key into digitalocean, giving in the name do_test

$ doctl compute ssh-key import do_test --public-key-file ~/.ssh/do_test.pub
ID Name FingerPrint
[id num] do_test [ --- fingerprint for ssh key --- ]

Finally, list all the ssh keys that digitalocean knows using

$ doctl compute ssh-key list

This listing shows all keys, including the FingerPrint, that will be needed below.

How to create a droplet

Now that the prerequisites are out of the way we can spin-up a droplet with a "simple" command. Seriously, there are still a variety of things to specify, but we're close to the end. A bash script can be included to accomplish a bunch of things that are important when we create a new droplet/VPS:

  • create a new, non-root user,
    • NOTE: this user will be added to the sudo group and will be required to set a password the first time that you ssh to the droplet. This way sudo can still be used on the droplet with password required-- the usual :)
  • copy over ssh keys and set permissions/ownership, and
  • disable root ssh login

Following here , the contents of the script ( call it my-script.sh ) should be something like this (be sure to change the username):

#!/bin/bash
set -euo pipefail

# CHANGE USERNAME
# -- this will be a sudo, not-root user
USERNAME=my_username

# - create user
# - immediately expire password to force a password change on login
useradd --create-home --shell "/bin/bash" --groups sudo "${USERNAME}"
passwd --delete "${USERNAME}"
chage --lastday 0 "${USERNAME}"

# Create SSH directory for sudo user and move keys over
home_directory="$(eval echo ~${USERNAME})"
mkdir --parents "${home_directory}/.ssh"
cp /root/.ssh/authorized_keys "${home_directory}/.ssh"
chmod 0700 "${home_directory}/.ssh"
chmod 0600 "${home_directory}/.ssh/authorized_keys"
chown --recursive "${USERNAME}":"${USERNAME}" "${home_directory}/.ssh"

# Disable root SSH login with password
sed --in-place 's/^PermitRootLogin.*/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config
if sshd -t -q; then systemctl restart sshd; fi

The actual creation of the droplet, using the script above, would have a command like this:

doctl compute droplet create MY-DROPLET \
--tag-names MY-TAG \
--image MY-IMAGE \
--region MY-REGION \
--size MY-SIZE \
--ssh-keys MY-SSH-FINGERPRINT \
--user-data-file my-script.sh \
--enable-ipv6 --enable-monitoring \
--enable-private-networking --enable-backups

where the following options can be set

  • MY-DROPLET: the name of the droplet/VPS
  • MY-TAG: a tag for the droplet/VPS
  • MY-IMAGE: the image used for the droplet/VPS. Options can be seen using
    $ doctl compute image list --format="ID, Slug, Name"
    ID Slug Name
    112379295 convoy Convoy 0.6.0 on Ubuntu 20.04
    113137715 nakama-18-04 Nakama 3.12.0 on Ubuntu 18.04
    114133748 bigcloud-odoo Odoo 14 on Ubuntu 20.04
    114445163 rstudio-20-04 RStudio 2021.09.2+382 on Ubuntu 20.04
    114537960 invoiceninja-20-0-4 Invoice Ninja 5 on Ubuntu 20.0.4
    116403190 appsmith-18-04 Appsmith 1.7 on Ubuntu 20.04
    116470971 npool nPool 1.8 on Ubuntu 20.04
    117726186 botguardo-botguardingressc BotGuard Ingress Controller 1.0 on Debian 11 (bullseye)
    117873390 intel-intelgprofilercr Intel gProfiler Crypto Demo 1.3 on Ubuntu 20.04
    --- cut-off ---
  • MY-REGION: the geographical location of the droplet. Options can be seen using
    $ doctl compute region list
    Slug Name Available
    nyc1 New York 1 true
    sfo1 San Francisco 1 false
    nyc2 New York 2 true
    ams2 Amsterdam 2 false
    sgp1 Singapore 1 true
    lon1 London 1 true
    nyc3 New York 3 true
    ams3 Amsterdam 3 true
    fra1 Frankfurt 1 true
    tor1 Toronto 1 true
    sfo2 San Francisco 2 true
    blr1 Bangalore 1 true
    sfo3 San Francisco 3 true
    syd1 Sydney 1 true
  • MY-SIZE: the size the droplet/VPS. Options can be seen using
    $ doctl compute size list --format="Slug, Description, Price Monthly, Price Hourly"
    Slug Description Price Monthly Price Hourly
    s-1vcpu-512mb-10gb Basic 4.00 0.005950
    s-1vcpu-1gb Basic 6.00 0.008930
    512mb Legacy Basic 6.00 0.008930
    s-1vcpu-1gb-amd Basic AMD 7.00 0.010420
    s-1vcpu-1gb-intel Basic Intel 7.00 0.010420
    s-1vcpu-1gb-35gb-intel Basic Intel 8.00 0.011900
    s-1vcpu-2gb Basic 12.00 0.017860
    1gb Legacy Basic 12.00 0.017860
    s-1vcpu-2gb-amd Basic AMD 14.00 0.020830
    --- cut-off ---
  • MY-SSH-FINGERPRINT: the fingerprint for the ssh key added above. This information is available using
    $ doctl compute ssh-key list
  • The last four flags are pretty self-explanatory, but more information can be found here . Almost certainly you want
    • --enable-ipv6 and
    • --enable-private-networking,
    but you probably want to check both
    • --enable-monitoring and
    • --enable-backups.
    Notably, backups are NOT free and are done daily by default.

A concrete example

Okay, let's try this out using the information above. The script I will use is secure-vps.sh containing:

#!/bin/bash
set -euo pipefail

# CHANGE USERNAME -- this will be sudo, not-root user
USERNAME=testuser

# - create user
# - immediately expire password to force a password change on login
useradd --create-home --shell "/bin/bash" --groups sudo "${USERNAME}"
passwd --delete "${USERNAME}"
chage --lastday 0 "${USERNAME}"

# Create SSH directory for sudo user and move keys over
home_directory="$(eval echo ~${USERNAME})"
mkdir --parents "${home_directory}/.ssh"
cp /root/.ssh/authorized_keys "${home_directory}/.ssh"
chmod 0700 "${home_directory}/.ssh"
chmod 0600 "${home_directory}/.ssh/authorized_keys"
chown --recursive "${USERNAME}":"${USERNAME}" "${home_directory}/.ssh"

# Disable root SSH login with password
sed --in-place 's/^PermitRootLogin.*/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config
if sshd -t -q; then systemctl restart sshd; fi

and the command given is (this spins up an Ubuntu image with nginx):

doctl compute droplet create test-droplet \
--tag-names test-tag \
--image nginx \
--region nyc1 \
--size s-1vcpu-1gb \
--ssh-keys [CENSORED] \
--user-data-file secure-vps.sh \
--enable-ipv6 \
--enable-private-networking

I put the above command into a bash script and ran the script, outputing:


$ ./create-droplet.sh 
ID Name Public IPv4 Private IPv4 Public IPv6 Memory VCPUs Disk Region Image VPC UUID Status Tags Features Volumes
477700144 test-droplet 1024 1 25 nyc1 Ubuntu NGINX 1.23.3 on Ubuntu 22.04 new test-tag droplet_agent

A list of droplets on my account, seconds later showed (IPs are censored for obvious reasons):

$ doctl compute droplet list
ID Name Public IPv4 Private IPv4 Public IPv6 Memory VCPUs Disk Region Image VPC UUID Status Tags Features Volumes
477700144 test-droplet [censored] [censored] [censored] 1024 1 25 nyc1 Ubuntu NGINX 1.23.3 on Ubuntu 22.04 [censored] active test-tag droplet_agent,private_networking,ipv6

Opening a browser and entering the "Public IPv4" for the url brought up the standard "Welcome to nginx!" page. Nice.

ssh to droplet

Next, we ssh to the droplet. A first step is to add this identity to my ~/.ssh/config file so that ssh knows the correct identity and key to use. I add the following entry

Host           do_test
HostName [censored Public IPv4 for droplet]
IdentityFile ~/.ssh/do_test
User testuser

With this entry, ssh to the droplet should be as simple as

$ ssh do_test

As noted above, the password for "testuser" will have to be set the first time that you ssh to the droplet-- the script sets it up so this is required. Be sure to create a strong password and save it carefully for use whenever sudo is needed on the droplet.

delete the droplet

After checking out the droplet as much as you'd like it is time to delete the droplet so that the charges don't add up. This can be done with (use left/right arrows to select "yes" when prompted):

$ doctl compute droplet delete test-droplet
❯ Are you sure you want to delete this Droplet? yes

You can check that test-droplet is not listed among the active droplet using

$ doctl compute droplet list

Well, that's it for this post. I've tried it all out as of Feb 20, 2025 and everything worked great!