Github: deploy to production
It is high time we should take our app to production. This could be a difficult section for beginners but once we start understanding it. It will be easier than building lightweight APIs.
I have created a light 4$ per month small Virtual Machine using Digitalocean. Try creating a small droplet and add your SSH key during droplet creation(recommended). Once your server is ready you can use ssh root@your_vm_public_ip to log into the server.
Once you are inside, you can create a new file using nano initial_server_setup.sh and put the below contents. I seriously advise reading each piece and understanding it to better estimate the security implications.
#!/bin/bash
set -euo pipefail
########################
### SCRIPT VARIABLES ###
########################
# Name of the user to create and grant sudo privileges
# put your desired username
USERNAME=algoholic
# Whether to copy over the root user's `authorized_keys` file to the new sudo
# user.
COPY_AUTHORIZED_KEYS_FROM_ROOT=true
####################
### SCRIPT LOGIC ###
####################
# Add sudo user and grant privileges
useradd --create-home --shell "/bin/bash" --groups sudo "${USERNAME}"
# Check whether the root account has a real password set
encrypted_root_pw="$(grep root /etc/shadow | cut --delimiter=: --fields=2)"
if [ "${encrypted_root_pw}" != "*" ]; then
# Transfer auto-generated root password to user if present
# and lock the root account to password-based access
echo "${USERNAME}:${encrypted_root_pw}" | chpasswd --encrypted
passwd --lock root
else
# Delete invalid password for user if using keys so that a new password
# can be set without providing a previous value
passwd --delete "${USERNAME}"
fi
# Expire the sudo user's password immediately to force a change
chage --lastday 0 "${USERNAME}"
# Create SSH directory for sudo user
home_directory="$(eval echo ~${USERNAME})"
mkdir --parents "${home_directory}/.ssh"
# Copy `authorized_keys` file from root if requested
if [ "${COPY_AUTHORIZED_KEYS_FROM_ROOT}" = true ]; then
cp /root/.ssh/authorized_keys "${home_directory}/.ssh"
fi
# Adjust SSH configuration ownership and permissions
chmod 0700 "${home_directory}/.ssh"
chmod 0600 "${home_directory}/.ssh/authorized_keys"
chown --recursive "${USERNAME}":"${USERNAME}" "${home_directory}/.ssh"
# Disable root SSH login with password
sed --in-place 's/^PermitRootLogin.*/PermitRootLogin prohibit-password/g' /etc/ssh/sshd_config
if sshd -t -q; then
systemctl restart sshd
fi
# Add exception for SSH and then enable UFW firewall
ufw allow OpenSSH
ufw allow 80
ufw allow 443
ufw --force enable
sudo apt update
# Install Docker
curl -fsSL https://get.docker.com | sudo sh
# Add your user to the docker group
sudo usermod -aG docker ${USERNAME}
# Install Docker Compose
sudo curl -L "https://github.com/docker/compose/releases/latest/download/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
# Test Docker and Docker Compose installation
docker --version
docker-compose --version
echo "Docker and Docker Compose setup completed."
Press ctrl+x to save and type Yes to save. You can execute this bash file by typing bash initial_server_setup.sh, when it's complete make sure you execute docker ps command and verify that we are not getting any errors. Now, ssh with your new username, you will have to reset a new password the first time and then again when you ssh username@your_vm_ip you should be inside.
Now, let's create some new files locally to efficiently deploy our code. First, I am going to start with docker-compose-prod.yml
version: '3.8'
services:
web:
build:
context: ./
dockerfile: Dockerfile
ports:
- "8000:8000"
environment:
- ENV_VAR_NAME=VALUE
depends_on:
- redis
volumes:
- ./:/app
command: uvicorn main:app --host 0.0.0.0 --port 8000
# removed --reload for production
restart: always
redis:
image: redis:latest
ports:
- "6379:6379"
nginx:
image: nginx:latest
ports:
- "80:80"
volumes:
- ./nginx/nginx.conf:/etc/nginx/nginx.conf
- ./nginx/conf.d:/etc/nginx/conf.d
depends_on:
- web
restart: always
The nginx service suggests that we need to create a directory named nginx where the main.py file is. Then nginx.conf file and a folder named conf.d inside the nginx directory. Note: conf.d is a folder and not a file!!
Put the below content in nginx.conf file:
worker_processes 4;
events { worker_connections 1024; }
http {
sendfile on;
server_tokens off;
server {
listen 80;
server_name localhost;
location / {
proxy_pass http://web:8000;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
}
If the conf.d directory is empty then git won't accept it and it won't be tracked. So, as a hack, I am putting a dummy .gitignore file inside of the conf.d folder.
# Ignore everything in this directory
*
# Except this file
!.gitignore
Use the below command to up docker-compose-prod services. Add these files, commit and push to GitHub/GitLab. Then clone your repo from GitHub to your server by using commands similar to below, you will only need to clone if you are using your own repo. If you are using my repo then you should checkout/reset to the below SHA code.
algoholic@algoholic:~$ git clone https://github.com/sourabhsinha396/codeshare.git
Cloning into 'codeshare'...
Resolving deltas: 100% (19/19), done.
algoholic@algoholic:~$ cd codeshare/
algoholic@algoholic:~/codeshare$ git reset --hard b8fe29f9013621f00ca4b706c9020bcec5f7bb10
HEAD is now at ac2281b fix nginx connecting port
Now, we can use the below docker-compose command to start the production services.
docker-compose -f docker-compose-prod.yml up
Visit: http://your-vm-public-ip to test the deployment in action :) If everything worked out successfully, you can start the server in detached mode in the background with
docker-compose -f docker-compose-prod.yml up -d
Note: Instead of docker-compose now use docker-compose -f docker-compose-prod.yml for every docker compose command.
Brige the gap between Tutorial hell and Industry. We want to bring in the culture of Clean Code, Test Driven Development.
We know, we might make it hard for you but definitely worth the efforts.
© Copyright 2022-23 Team FastAPITutorial