Hosting side-projects with docker-compose

Hosting side-projects, especially if they consist of multiple services, can be a real pain. This post explains how that can be a lot easier with docker-compose.

Introduction

Side-projects usually don’t need a complex hosting environment. They rather need one that is cheap, as they mostly don’t make money. In most cases self hosting them on a VPS is the cheapest option, but it can be time-consuming. Docker already makes this process a lot easier to handle, and docker-compose is the next step.

The side-project I did this for, IsMyDependencySafe, consists of an Asp.Net Core api and an ElasticSearch database. Caching, encryption and all other web aspects are handled by nginx.

docker-compose

Lets talk a bit about how docker-compose works first.

Basically, it is a tool that enables you to put the configuration usually passed with command-line arguments in a config file, and create/shutdown multiple containers at once.

As docker run commands can get very long very quickly, putting all config options in a docker-compose.yml makes it a lot more readable, and eases debugging (no more scrolling through the bash history to find the last docker run command that worked).
I even started to use docker-compose for applications that need just one container.

Note: docker-compose usually is not installed with docker, check its install page for more information.

docker-compose.yml

The docker-compose.yml file is the default configuration file name. You can name it whatever you want, but this way docker-compose will find it without passing a parameter for it. It specifies containers and their configurations, like environment variables, network and so on.

Here is an example docker-compose.yml:

version: '3'
services:
  nginx: 
    image: nginx:latest
    container_name: production_nginx
    volumes:
      - ./nginx/nginx.conf:/etc/nginx/nginx.conf
    ports:
      - 80:80
      - 443:443

  elasticsearch:
    image: docker.elastic.co/elasticsearch/elasticsearch:latest
    container_name: production_elasticsearch
    volumes:
      - esdata:/usr/share/elasticsearch/data
    expose:
      - "9200"
      
  ismydependencysafe:
    image: ismydependencysafe:latest
    container_name: production_ismydependencysafe
    volumes:
      - ./ismydependencysafe/appsettings.Production.json:/app/appsettings.Production.json
    expose:
      - "80"
      
volumes:
  esdata:
    driver: local

Each entry in services specifies a container. image, volumes and ports are specified just like in a docker run command.

One interesting piece here is that although no network is specified, all containers are in the same one, which is the default behavior.
Since docker allows to access containers within the same network by their name, it is possible to set production_elasticsearch:9200 as path for ElasticSearch. So there is no need to specify an absolute url! This is also the way nginx is configured to proxy calls to ismydependencysafe.

Starting and stopping

The containers are defined, so 99% of the work is done. The last percent, starting and stopping, is also the easiest. Just navigate to the directory that contains the docker-compose.yml and execute

docker-compose up -d

-d starts the containers detached. To see the outputs of all of them, start it without that flag.

To stop, just execute

docker-compose down

in the same directory.

Advantages of docker-compose

  • The whole infrastructure is defined in one file
    • This is the biggest advantage over separate docker run commands in my opinion. It provides a good overview of the services needed for the project and how they are mapped together. Also not having to copy&paste several commands every time the service needs to be restarted is a big plus.
  • The only public facing container is nginx
    • It reverse proxies to the other services, which are not publicly available, which improves security.
    • Another benefit is that caching, SSL and every other webserver related setting has a central configuration.
    • This makes it possible to easily start the whole setup a second time, just by changing the port mappings from 80:80 to 81:80 and 443:443 to 444:444, which is very useful for testing.

Summary

There’s not much more to say, except that docker-compose is awesome, and if you’re using docker without it, start using it. It’s not complicated, and will make your life so much easier!