3 min read

Digital Ocean Droplet setup

Table of Contents

Everything I need to not forget.

Digital Ocean

Droplet Creation

Create a Droplet with Docker already installed. Add an ssh key to the droplet, during creation already.

To authenticate: Go to GitHub → Settings → Developer Settings → Personal Access Tokens (Classic) Create a token with repo permissions. Configure git to use the token for authentication:

git config --global credential.helper store

And then clone a repository, and enter the username and PAT.

Neovim, Lazyvim

curl -LO https://github.com/neovim/neovim/releases/latest/download/nvim-linux-x86_64.appimage
chmod u+x nvim-linux-x86_64.appimage
./nvim-linux-x86_64.appimage

mkdir -p /opt/nvim
mv nvim-linux-x86_64.appimage /opt/nvim/nvim

Then:

nano ~/.bashrc
# Add this:
export PATH="$PATH:/opt/nvim"
# Save with CtrlO and close with CtrlX
source ~/.bashrc

Then:

mv ~/.config/nvim{,.bak}
git clone https://github.com/LazyVim/starter ~/.config/nvim
rm -rf ~/.config/nvim/.git
nvim

System Optimizations

Disable multipathd

multipathd is a daemon for servers with multiple physical paths to storage (e.g. SAN failover). On a single-disk VPS it has nothing to do, but still polls block devices continuously — causing high CPU and disk I/O for no reason. Disable it:

systemctl stop multipathd
systemctl disable multipathd

On a 1GB DigitalOcean droplet this alone dropped CPU usage from ~40% to ~3% and disk I/O from ~150 MB/s to near zero.

Add a swap file

A 1GB droplet with no swap will OOM-kill processes under memory pressure. Add a 1GB swap file:

fallocate -l 1G /swapfile
chmod 600 /swapfile
mkswap /swapfile
swapon /swapfile
echo '/swapfile none swap sw 0 0' >> /etc/fstab

The last line makes it persist across reboots.

Github Actions

Secrets

Go to Settings -> Secrets and variables -> Actions -> New repository secret, and add the following secrets:

  • DIGITAL_OCEAN_DROPLET_IP
  • DIGITALOCEAN_SSH_PRIVATE_KEY
  • DIGITALOCEAN_SSH_USER (root for personal use, otherwise create a new user)
  • SSH_PASSWORD (If the local ssh key is password protected, otherwise leave it empty)

Action

Whenever something gets pushed to main, it gets pulled to opt/thesis in the droplet. I used this action for some containers I was running for data-scraping for my thesis, hence the directory name. It then builds and gets the compose containers up, with environment variable PRODUCTION_ENV = True.

name: Deploy to DigitalOcean

on:
  push:
    branches: [main]

jobs:
  deploy:
    runs-on: ubuntu-latest

    steps:
      - name: Checkout code
        uses: actions/checkout@v4

      - name: Deploy via SSH
        uses: appleboy/ssh-action@v1.0.0
        with:
          host: ${{ secrets.DIGITALOCEAN_DROPLET_IP }}
          username: ${{ secrets.DIGITALOCEAN_SSH_USER }}
          key: ${{ secrets.DIGITALOCEAN_SSH_PRIVATE_KEY }}
          passphrase: ${{ secrets.SSH_PASSWORD }}
          script: |
            cd /opt/thesis
            git pull origin main
            PRODUCTION_ENV=True docker compose up -d --build