Each weekend I want to try out small projects using different tech stacks/technologies. This week let’s deploy a Linux box on DigitalOcean and set it up so we can run multiple Docker containers for future weekend projects.

1. Setup Docker and Nginx

Install docker:

https://www.digitalocean.com/community/tutorials/how-to-install-and-use-docker-on-ubuntu-18-04

Check if docker is running

1
sudo systemctl status docker

press q to exit

Install nginx

https://www.digitalocean.com/community/tutorials/how-to-install-nginx-on-ubuntu-18-04

if you run

1
2
sudo ufw allow 'Nginx HTTP'
sudo ufw status

and it shows Status: inactive - that means the firewall is inactive. Enable the firewall by running

1
2
sudo ufw allow ssh
sudo ufw enable

now run sudo ufw status again. Check if nginx is running using systemctl status nginx

press q to exit

2. Docker container for Nodejs projects

Create a small Nodejs project

Create a Hello World project from the nodejs folder:

1
2
mkdir nodejs
cd nodejs

Create a package.json (nano package.json) with these content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
{
  "name": "nodejs",
  "version": "1.0.0",
  "description": "nodejs project",
  "author": "Cuong Tran",
  "license": "MIT",
  "main": "app.js",
  "keywords": ["nodejs", "bootstrap", "express"],
  "dependencies": {
    "express": "^4.16.4"
  }
}

Install npm apt install npm then run npm install

Create a file named appjs (nano app.js):

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
const express = require("express");
const app = express();

const port = 8080;
app.get("/", (req, res) => {
  res.send("Hello World!");
});

app.listen(port, () => {
  console.log(`Example app listening on port ${port}`);
});

Allow traffic to port 8080:

sudo ufw allow 8080

Create a Docker image/container for the Nodejs project

Create Dockerfile nano Dockerfile - add in the content

1
2
3
4
5
6
7
8
9
FROM node:20-alpine
RUN mkdir -p /app/node_modules && chown -R node:node /app
WORKDIR /app
COPY package*.json ./
RUN npm ci
COPY --chown=node:node . .
EXPOSE 8080
USER node
CMD [ "node", "app.js" ]

Create a .dockerignore file (nano .dockerignore) to specify which files and directories in the project directory should not be copied over to the container.

1
2
3
node_modules;
npm - debug.log;
Dockerfile.dockerignore;

Build the application image using the docker build command

docker build -t cuongt/nodejs-20-alpine .

Run docker images to view the available images

We now can create a container with this image - run:

1
docker run --name nodejs-20-alpine -p 8080:8080 -d cuongt/nodejs-20-alpine

Run docker ps -a to view all the containers

docker stop/start container_name to stop/start

dockr rm container_name to remove a container

We are now done with setting a container for our Nodejs project, let’s create another container for Python.

3. Docker container for Python projects

Create a sample Python project

Create a folder for Python from root folder:

mkdir python

cd python

Create an app.py (nano app.py) with these content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
from flask import Flask
import os
import socket

app = Flask(__name__)

@app.route("/")
def hello():
    html = """Hello {name}!
    Hostname: {hostname}"""
    return html.format(name=os.getenv("NAME", "world"), hostname=socket.gethostname())

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=8081)

Create a uwsgi.ini file. This file will contain the uWSGI configurations for our application. uWSGI is a deployment option for Nginx that is both a protocol and an application server; the application server can serve uWSGI, FastCGI, and HTTP protocols.

nano uwsgi.ini

1
2
3
4
[uwsgi]
module = main
callable = app
master = true

Create a requirements file so we can run later:

nano requirements.txt - add Flask to the requirements:

Flask>=2.0.2

Setting Up Docker

Create a Dockerfile nano Dockerfile:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
FROM python

WORKDIR /app

ADD . /app

RUN pip install -r requirements.txt

EXPOSE 8081

ENV NAME PythonApp

CMD ["python", "app.py"]

Build the Python docker image and container

With these 2 commands:

1
2
docker build -t cuongt/python .
docker run --name python -p 8081:8081 -d cuongt/python

We now finished setting up the Python project and its Docker container. Let create server blocks for both Nodejs and Python containers.

4. Create server blocks

Create a new file called nodejs.cuongt.com.conf in the /etc/nginx/sites-available/ directory and add the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
  listen        80;
  listen        [::]:80;
  server_name   nodejs.cuongt.com;

  location / {
    proxy_pass  http://localhost:8080;
    include proxy_params;
  }
}

Create a new file called python.cuongt.com.conf in the /etc/nginx/sites-available/ directory and add the following content:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
server {
  listen        80;
  listen        [::]:80;
  server_name   python.cuongt.com;

  location / {
    proxy_pass  http://localhost:8081;
    include proxy_params;
  }
}

Once you have the two config files ready cd to the /etc/nginx/sites-enabled directory, and run the following commands:

1
2
ln -s ../sites-available/nodejs.cuongt.com.conf .
ln -s ../sites-available/python.cuongt.com.conf .

Restart Nginx for it to take effects of new changes:

systemctl restart nginx

The 2 domain names nodejs.cuongt.com and python.cuongt.com are pointed to the droplet using A records with the droplet IP.

5. Use Docker Compose to manage our containers

Create a docker-compose.yaml file:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
version: '3.8'

services:
  nodejs-app:
    build:
      context: ./nodejs
    container_name: nodejs-prd
    ports:
      - "8080:8080"
    environment:
      - NODE_ENV=production
    volumes:
      - ./nodejs:/app
    restart: unless-stopped

  flask-app:
    build:
      context: ./python
    container_name: python-prd
    ports:
      - "8081:8081"
    environment:
      - FLASK_ENV=production
    volumes:
      - ./python:/app
    restart: unless-stopped

Enable Docker to start on reboot:

1
systemctl enable docker

6. Setup SSL certificates

Add SSL Certificates with this link: https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-22-04