DigitalOcean's Logo.
Published Nov 20, 2022
1,037

Hosting on DigitalOcean's Droplets

DigitalOcean Droplets are Linux-based virtual machines (VMs) that run on top of virtualized hardware. Each Droplet you create is a new server you can use, either standalone or as part of a larger, cloud-based infrastructure.

Having a personal droplet on DigitalOcean is great. You can host many apps, long-running processes, and side projects without having to worry about physical hardware, networking, and energy. You pay a flat, predictable fee based on the specs of your droplet, and that's it. Combine your droplet with services from providers like Cloudflare, and you're in for a pretty exceptional experience.

I pay ~$12/mo. for a shared CPU / 2 GB RAM / 50 GB Disk droplet running in DigitalOcean's NYC1 region, and over the past year I've used it to host internal tools, small game servers, and staging versions of web applications. If I need to get something up and running quickly, the process is actually rather simple: Create a new user, paste in a docker-compose file, start it, and lastly possibly configure Nginx and Cloudflare accordingly.

In this guide, I'll be showing you how to set up a service like Umami Analytics and self-host it on your droplet, using Cloudflare for DNS and protection. Let's take a look!

Note

This guide presumes that you have some basic knowledge in using a terminal, SSH keys, Linux commands, and a bit of Docker. We'll be interacting with the actual droplet mostly through a web interface, but everything else will be done from the terminal. DigitalOcean has great documentation and support, and if you're stuck, chances are a similar problem has already been answered on their forum.

Getting Started

Make sure you've created a DigitalOcean account, and start by creating a new droplet. Choose the region and datacenter nearest to you, and for the image I've gone with Ubuntu. Despite my general disliking for Ubuntu, it's the best supported and the rest of this guide will also be based on Ubuntu. For the droplet size, you can go with anything, I'd recommend starting off with the $0.009/hour option, and then possible upgrading if you require more resources.

Using an SSH key is definitely the best option, and DigitalOcean provides pretty clear instructions on how to manage these keys on Linux, MacOS, and Windows. Lastly, give your droplet an identifying name you'll remember, and hit Create Droplet. And there you have it, your droplet will be finalized soon.

Open the droplet dashboard, and click on the 'Console' button in the top right. We now have access to the terminal.

Now of course, we could do everything from the web console, but wouldn't it be nice if we could somehow connect from a terminal on our machine? This is where the SSH key comes into play, and if it's properly configured, you should be able to do something along the lines of this:

sh
ssh root@(insert your droplet ip)

Now that we have access to our droplet, let's see how we can get a web application running on it. I'm going to use Umami Analytics as an example, a service that I'm actually running on my personal droplet for this site. It's easy to self-host, and they have an example with Docker. This process can apply to any other application really, I just see Umami as a good example because we'll need to do the following:

  • Set up Docker and docker-compose
  • Create a new user for the service
  • Start the docker-compose file and services
  • Configure NGINX so we can reach Umami from the internet
  • Point our DNS (for this guide, Cloudflare) to the droplet

Running Umami

Setting up Docker

Before we can get to running any applications, we need to make sure Docker is properly configured. Of course, you can use literally anything else to run your stuff, but Docker has quite a few key advantages, mainly isolation, performance, and portability. This works especially great for a configuration like what we have with our droplet.

DigitalOcean has an official article on setting up Docker, which I highly suggest you read for clarification, as I'll just be including the necessary commands without any sort of an explanation:

batchfile
sudo apt update
sudo apt install apt-transport-https ca-certificates curl software-properties-common
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo apt-key add -
sudo add-apt-repository "deb [arch=amd64] https://download.docker.com/linux/ubuntu focal stable"
sudo apt install docker-ce

Lastly, check if Docker is running with:

batchfile
sudo systemctl status docker
Installing Docker now gives you not just the Docker service (daemon) but also the docker command line utility, or the Docker client.

Creating a Separate User

It's always a good idea to isolate your environments on a server, and regardless, we don't want these containers to be started from the root user, even though Docker itself requires root access. I'm going to be creating an umami user with the adduser command:

batchfile
root@tutorial:~# sudo adduser umami
Adding user `umami' ...
Adding new group `umami' (1000) ...
Adding new user `umami' (1000) with group `umami' ...
Creating home directory `/home/umami' ...
Copying files from `/etc/skel' ...
New password:
Retype new password:
passwd: password updated successfully
Changing the user information for umami
Enter the new value, or press ENTER for the default
	Full Name []:
	Room Number []:
	Work Phone []:
	Home Phone []:
	Other []:
Is the information correct? [Y/n] Y
root@tutorial:~#

Now, make sure to add the umami user to the Docker group so that we can run the docker command:

batchfile
sudo usermod -aG docker umami

Lastly, switch to the user and test that we can run Docker:

batchfile
root@tutorial:~# su - umami
umami@tutorial:~$ docker ps
CONTAINER ID   IMAGE     COMMAND   CREATED   STATUS    PORTS     NAMES
umami@tutorial:~$

You can switch to the root user anytime by using exit.

Docker-Compose

We're going to be using docker-compose to run the postgresql database and Umami server at the same time. The configuration for everything will be defined in one docker-compose.yml file, making things super simple for us.

Install docker-compose by running the following command as the root user:

batchfile
apt install docker-compose

Now, switch back to the umami user, you'll need to use a terminal editor like nano or vim, both of which should come preinstalled by default. Simply nano docker-compose.yml or vim docker-compose.yml, and paste the following contents, setting the hash salt and postgres password:

YAML
# docker-compose.yml
version: "3.3"

services:
  umami:
    image: docker.umami.dev/umami-software/umami:postgresql-latest
    ports:
      - "6900:3000"
    environment:
      DATABASE_URL: postgresql://umami:<REPLACE ME PASSWORD>@db:5432/umami
      DATABASE_TYPE: postgresql
      HASH_SALT: <REPLACE ME HASH SALT>
    depends_on:
      - db
    restart: always
  db:
    image: postgres:12-alpine
    environment:
      POSTGRES_DB: umami
      POSTGRES_USER: umami
      POSTGRES_PASSWORD: <REPLACE ME PASSWORD>
    volumes:
      - ./sql/schema.postgresql.sql:/docker-entrypoint-initdb.d/schema.postgresql.sql:ro
      - umami-db-data:/var/lib/postgresql/data
    restart: always
volumes:
  umami-db-data:

I'm also going to be mapping the container's 3000 port to 6900 on the host machine, which is important to keep in mind as this is the port that we expose.

Now, with just one command, we'll be able to fetch all of the images, configure the environment, and start up the server and database:

batchfile
docker-compose up -d

Now, our containers are running. Pretty sweet!

batchfile
Creating umami_db_1 ... done
Creating umami_umami_1 ... done
umami@tutorial:~$

At this point, we can actually see the application running in our browser if we visit [droplet IP]:6900

The problem with this is first of all, we're connecting to it directly via an ip address and port, and also that there's no TLS and the site is not secure. Let's fix that by adding Nginx and proxying our site through Cloudflare.

NGINX

Nginx is a web server that can also be used as a reverse proxy, load balancer, mail proxy and HTTP cache.

Domain

This section presumes that you have access to a domain on a service like Cloudflare. You'll need to be able to read and modify its DNS records in order to be able to point your domain to the droplet's IP address.

We'll be using Nginx as a reverse proxy to deliver users to the right application server running on our droplet. In our case, we only have our one Umami app running, but let's take a look at an example.

Say we have a domain, example.com, and when a user visits that domain they're taken to our web server running on our droplet. This is easy enough to do, as the default HTTP port is 80. If we run our server on port 80, and create a DNS A record on our domain, pointing to the droplet's IP address, sure enough it works!

Now let's say we want to run an additional server on our droplet, port 80 is already in use, so we'll have to go with something else, like 8080. Now, let's have two.example.com point to our second server, and add another A record pointing to [droplet-IP]:8080. Except there's a problem: we can't specify a port number in the record, and using just the IP address takes us to the server hosted on port 80. So how the hell do we get this second server to work?

This is where a reverse proxy like Nginx comes in, all requests to the droplet IP will first pass through the reverse proxy, which decides which application server to route the request to, based on the url.

Again, DigitalOcean has a tutorial on this, so feel free to reference that while I detail a much more shortened version of the set up process. From the root, install the package with:

batchfile
apt install -y nginx

And make sure that that Nginx has access through the firewall with:

batchfile
ufw allow 'Nginx HTTP'

Next, we'll want to create and edit the file server.conf in the nginx directory:

batchfile
nano /etc/nginx/sites-available/server.conf

In this file, I'll add a single server block which will use the address of umami.your_domain.com:

text
server {
  server_name umami.your_domain.com;

  location / {
    proxy_pass http://localhost:6900;
    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
  }
}

As you can see, we are proxying to the local port 6900 and the X-Real-IP and X-Forwarded-For headers are coming from Cloudflare. Since Umami is for analytics, knowing the client's true IP address is helpful, but requires a bit more additional set up. Cloudflare already has a guide for this, and specifically for Nginx, you'll need to use the ngx_http_realip module for each one of Cloudflare's IPs (cloudflare.com/ips).

This means editing /etc/nginx/nginx.conf and in the http block prepending the following:

text
http {
  set_real_ip_from 103.21.244.0/22;
  set_real_ip_from 103.22.200.0/22;
  set_real_ip_from 103.31.4.0/22;
  set_real_ip_from 104.16.0.0/13;
  set_real_ip_from 104.24.0.0/14;
  set_real_ip_from 108.162.192.0/18;
  set_real_ip_from 131.0.72.0/22;
  set_real_ip_from 141.101.64.0/18;
  set_real_ip_from 162.158.0.0/15;
  set_real_ip_from 172.64.0.0/13;
  set_real_ip_from 173.245.48.0/20;
  set_real_ip_from 188.114.96.0/20;
  set_real_ip_from 190.93.240.0/20;
  set_real_ip_from 197.234.240.0/22;
  set_real_ip_from 198.41.128.0/17;
  set_real_ip_from 2400:cb00::/32;
  set_real_ip_from 2606:4700::/32;
  set_real_ip_from 2803:f800::/32;
  set_real_ip_from 2405:b500::/32;
  set_real_ip_from 2405:8100::/32;
  set_real_ip_from 2c0f:f248::/32;
  set_real_ip_from 2a06:98c0::/29;
  
  # NOTE: Cloudflare's list of IP addresses is always being updated,
  # make sure you're up to date (https://www.cloudflare.com/ips)
  
  # use any of the following two
  
  # real_ip_header CF-Connecting-IP;
  real_ip_header X-Forwarded-For;
  
  # Everything else...
}

Make sure to link this file to sites-enabled with the following command, so that it is detectable by Nginx:

batchfile
sudo ln -s /etc/nginx/sites-available/server.conf /etc/nginx/sites-enabled/

Now, test the configuration with nginx -t and make sure the result is successful:

batchfile
root@tutorial:/etc/nginx# nginx -t
nginx: the configuration file /etc/nginx/nginx.conf syntax is ok
nginx: configuration file /etc/nginx/nginx.conf test is successful

Now, we can restart the service and use the configurations we specified by running:

batchfile
service nginx restart

And that's it for Nginx! Now, all we have to do is point Cloudflare to our droplet, and we're almost done.

Cloudflare

Create an A record pointing to your droplet's IP address, and specify the same subdomain (or root if that's what you used) as in the Nginx config:

I'll keep the traffic as proxied, so that Cloudflare manages our SSL certificate and we don't have to worry about certificates on our droplet. Now, we can visit our site, and we'll be greeted with the same Umami as before.

Pretty cool right? One of the great things about Cloudflare is that their infrastructure is capable of mitigating large-scale DDOS attacks. Our umami server is now also protected, but we can still access it via the original droplet IP and 6900 port. If someone discovered our droplet's IP address, which would not be difficult to do at all, they could attack our server directly, avoiding Cloudflare altogether.

This last step is to set up a firewall on DigitalOcean so that only Cloudflare's list of IP addresses will be able to connect to our droplet.

Droplet Firewall

In the droplet console on DigitalOcean, head over to the Networking tab on the left side. Under Firewalls, hit 'Edit' and then 'Create Firewall'. I'm going to call this one cloudflare-only. From there, add all of Cloudflare's IPv4 and IPv6 addresses as inbound rules for HTTP and HTTPS. You should end up with something that looks like this:

Lastly, add your droplet to the firewall. Now, you should see that accessing your site through the domain and Cloudflare works, but the direct IP address gets blocked. Exactly what we want.

Conclusion

You now have an app like Umami running on your droplet, without the hassle of managing SSL certificates. It's fully secure and protected by Cloudflare, and cannot be accessed directly. It's online 24/7, you pay a flat fee based on the VM's resources, and you can even enable a reserved IP address for the droplet. DigitalOcean is in many ways for me, my ideal internet.

Let's say you now want to run something else on your server, all you'd need to do is repeat some of what we just did:

  • Create a new user, add it to the docker group
  • Start containers with docker-compose or similar
  • Add another server block within /etc/nginx/sites-available/server.conf and restart Nginx
  • Add another A record pointing your domain to the droplet's IP address

...and that's it!

My Favorite Uses

Droplets obviously aren't for everything, I tend to avoid hosting direct production applications on my personal server, but the Umami running on this site is self-hosted on my droplet. In addition to that, here are a few other things I suggest running:

  • Tailscale - Tailscale is absolutely great. I use my droplet as an exit node which lets me use it like a VPN, but also I can access the server's internal ports and applications as long as I'm connected to the network. With the firewall, I can have the rest of the droplet closed down to the internet except when I specifically expose something, but access internal tools through Tailscale. Check it out!
  • Small hobby projects and game servers that aren't under heavy load, but need to be running all the time, like my old project QSpy, an online version of Spyfall.
  • Personal file servers, like NextCloud or Linx.
  • Personal URL shorteners for your domain, like this one I wrote. Link.

Destroying the Droplet

Destroying your droplet is as easy as going to the 'Destroy' tab in the dashboard, and hitting 'Destroy'. Everything is immediately cleaned up and all of the static droplet IPs are freed.

Next Post

DevTooling Discord