Deploy to VPS with Docker
For me, deploying web applications is hard and complex. I always re-read stuff every time. This article assumes Ubuntu 20.10 as host server. You can use DigitalOcean to host your servers. The technologies are:
- Docker and Docker Compose
- Nginx
All CAPITALIZED texts are supposed to be replaced with proper values.
Summary
Configure the Application
- Ensure that production credentials are not in the repository, create a separate
.env.production
. - Configure the filepath of encryption keys to use pattern
ANY_ABSOLUTE_PATH/keys/
.
For this step I recommend having separate Docker configurations for development environments and use the default for production, so you would end up with
Dockerfile
(production)Dockerfile.dev
docker-compose.yml
(production)docker-compose.dev.yml
.dockerignore
(files to ignore in containers)entrypoint.sh
(application startup script)
Create Dockerfile
Find a proper image for you application at https://hub.docker.com/. Be as specific as possible.
FROM IMAGE_NAME:VERSION
# System install.
RUN apt install PACKAGE_NAME -y # e.g. texlive
# Dependencies and Build Commands
WORKDIR /code
COPY DEPENDENCY_FILE /code/ # e.g. package.json
RUN INSTALL_COMMAND # e.g. yarn install
# Copy your code to container if needed.
COPY . /
Create docker-compose.yml
In production, you usually don't put your databases here because you should be able to run several containers of same application. I recommend using managed databases.
version: "VERSION_NUMBER"
services:
# such as database, for development and prototype environments
SERVICE_NICKNAME_1:
image: IMAGE_NAME
env_file:
- .env
restart: always # you can omit for development
SERVICE_NICKNAME_2:
image: IMAGE_NAME
env_file:
- .env
web:
build:
context: .
dockerfile: Dockerfile # for development use Dockerfile.dev
command: /code/entrypoint.sh
depends_on:
- SERVICE_NICKNAME_1
- SERVICE_NICKNAME_2
env_file:
- .env
ports:
- "APPLICATION_PORT:CONTAINER_PORT" # e.g. "8000:8000"
restart: always
volumes:
- .:/code
- BLOCK_STORAGE_PATH:/code/block-storage
Remember to start your application at 0.0.0.0
, then it will be visible outside. For example:
# entrypoint.sh
gunicorn APP_STARTUP_FILE:app --host 0.0.0.0 --port CONTAINER_PORT -w 4 -k uvicorn.workers.UvicornWorker
Server Setup
Generate SSH Key
The keys are usually located at ~/.ssh
.
cd ~/.ssh
mkdir APP_NAME
ssh-keygen -t ed25519 -f ./APP_NAME/KEY_NAME
# e.g. ./ecommerce/main_digital_ocean
Create the Server
- Go to you cloud platform and create a server there.
- Open the public SSH key
~/.ssh/APP_NAME/CLOUD_NAME.pub
- Add that as your server SSH key.
WARNING: Do not enable password access.
Firewall
Immediately after the server is created, add the firewall. This can usually be done in the cloud provider's dashboard. Add the following rules:
Port | Type | Sources |
---|---|---|
22 | SSH | YOUR_IP_ADDRESS |
443 | HTTPS | 0.0.0.0 |
The rules are to prevent SSH connections from unknown IP address. You may need to expose other ports depending on your application. There exists serverless setups that require database to be exposed, I recommend not to do that.
SSH Config File
- Obtain your server IP-address from cloud dashboard.
- Open
~/.ssh/config
- Add the following lines:
Host APP_NAME SERVER_IP_ADDRESS
HostName SERVER_IP_ADDRESS
User webapp
IdentityFile ~/.ssh/APP_NAME/CLOUD_NAME.pem
https://linux.die.net/man/5/ssh_config
Create a non-root user
ssh root@APP_NAME
adduser webapp
usermod -aG sudo webapp
rsync --archive --chown=webapp:webapp ~/.ssh /home/webapp
exit
https://www.digitalocean.com/community/tutorials/initial-server-setup-with-ubuntu-20-04
Update the System
ssh APP_NAME
sudo apt update
sudo apt upgrade
Install Docker and Docker Compose
Just follow the official instructions, commands are not added here because they are complex and may change:
https://docs.docker.com/engine/install/ubuntu/#install-using-the-repository
Setup the Application
The application should be in Git repository host like GitLab or GitHub. Add a read-only SSH key to your repository. GitHub calls them deploy keys:
https://docs.github.com/en/developers/overview/managing-deploy-keys#deploy-keys
Clone your application somewhere, I suggest ~/projects
.
Environment Variables
Create ~/projects/APP_NAME/.env
file and put your credentials and configurations there.
Application Start
Then start your application:
docker-compose build
docker-compose up -d
https://docs.docker.com/compose/production/
Point your domain to the server
Go to DNS provider you use (could be same as domain registrar), add A record
that point to your server IP.
The IP may change if you shut down your server, in that case you need to update it or reserve an IP (usually one or two euros a month).
Install Nginx
sudo apt install nginx
Configure Nginx
Open file at /etc/nginx/sites-available/APPLICATION_DOMAIN/nginx.conf
http {
server {
listen 80;
server_name APPLICATION_DOMAIN;
location / {
proxy_pass http://127.0.0.0:APPLICATION_PORT;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
limit_except GET HEAD POST PUT PATCH DELETE OPTIONS { deny all; }
}
}
}
HTTPS Certificates
Follow the instructions: https://certbot.eff.org/lets-encrypt/ubuntufocal-nginx
Choose the option to redirect HTTP to HTTPS. After the certbot is done, your site should be up.