24
loading...
This website collects cookies to deliver better user experience
curl -fsSL https://get.docker.com -o get-docker.sh # Download install script.
sudo chmod u+x ./get-docker.sh # Make script executable.
sudo ./get-docker.sh
sudo usermod -aG docker $USER # Add current user to the docker group.
newgrp docker # Reload groups so that changes take effect.
docker ps
to verify that Docker is installed correctly. You should see something like this.ubuntu@ip-172-31-38-160:~$ docker ps
CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
sudo curl -L "https://github.com/docker/compose/releases/download/1.29.2/docker-compose-$(uname -s)-$(uname -m)" -o /usr/local/bin/docker-compose
sudo chmod +x /usr/local/bin/docker-compose
docker-compose up -d
and waiting for the images to download and build. Docker will pull the NGINX and Postgres images, as well as build the image for the app container.docker ps
after the image building and downloading is complete. The output should be similar to the below.CONTAINER ID IMAGE COMMAND CREATED STATUS PORTS NAMES
0cc1d1798b49 nginx "/docker-entrypoint.…" 4 seconds ago Up 3 seconds 0.0.0.0:80->80/tcp, :::80->80/tcp openranktracker_nginx_1
eb3679729398 open-rank-tracker "python tasks.py wor…" 51 seconds ago Up 49 seconds openranktracker_app-background_1
ab811719630a open-rank-tracker "gunicorn --preload …" 51 seconds ago Up 49 seconds openranktracker_app_1
df8e554d7b12 postgres "docker-entrypoint.s…" 52 seconds ago Up 50 seconds 0.0.0.0:5432->5432/tcp, :::5432->5432/tcp openranktracker_database_1
68abe4d03f62 redis:5.0.4-stretch "docker-entrypoint.s…" 52 seconds ago Up 50 seconds 6379/tcp openranktracker_redis_1
Dockerfile
and docker-compose.yml
files contain all of the relevant details. The first contains instructions for building the Flask API container, and the second specifies all of the images that make up the application.docker-compose.yml
as well as docker-compose.prod.yml
. This is how we'll manage the differences in deployment between development and production versions. There are typically several important differences between environments, such as how SSL certificates are handled.nginx.conf
is configured first.worker_processes 4;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
server {
listen 80;
listen [::]:80;
location / {
root /static;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, public, must-revalidate, proxy-revalidate";
}
location /api {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_pass http://unix:/sock/app.sock:/api;
}
}
}
server
block tells NGINX to listen on port 80, while the location
blocks define what should happen when a request URL matches a certain pattern. The order of location blocks is important – the first block can match any request, but the second block is more specific and applies to requests starting with /api
as their path.proxy_pass
directive. The http://unix:/sock/
means that the network traffic will be over a Unix domain socket. The app.sock
is a file that is shared between NGINX and Flask – both read and write from this domain socket file to communicate. Lastly, :/api
means that the receiving side, Flask, should get requests prefixed with /api
.X-Forwarded-Proto
component will become important later when we introduce SSL in our production configuration. This directive will cause NGINX to proxy requests with the same protocol, so if a request was made over HTTPS, then Flask will receive that same request over HTTPS. This is important when implementing features like signing in with Google, because OAuth libraries require that every request be made over SSL.docker-compose.yml
file that defines how NGINX and Flask are deployed.version: '3'
volumes:
sock:
services:
nginx:
image: nginx
restart: always
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf
- sock:/sock
ports:
- "80:80"
app:
command: gunicorn --preload --bind=unix:/sock/app.sock --workers=6 wsgi
restart: always
image: open-rank-tracker
build: .
volumes:
- sock:/sock
sock
volume definition. By declaring sock
as a top-level volume, we can share it between NGINX and Flask, allowing them to use it as a Unix domain socket.index.html
file before we can really do any testing. Create an index.html
file under the static directory within the project root.sudo touch static/index.html
sudo bash -c 'echo "Hi, world" > static/index.html'
curl http://localhost
http://localhost
(or to the IP of your server if deployed elsewhere) in your browser should show Hi, world
in response. This means the request matched the first location block in nginx.conf
– in fact, any request you send that doesn't start with /api
should return Hi, world
at this point.http://localhost/api
in your browser, you'll see the Flask 404 page instead. We haven't defined any routes in Flask yet, so the 404 is expected, but we know that NGINX and Flask are configured properly at this point.docker-compose.yml
configuration below, and walk through a few of the most important sections.database:
image: postgres
restart: always
volumes:
- /var/lib/postgres:/var/lib/postgres
expose:
- 5432
env_file:
- variables.env
database
, which is important, because that's the host name other containers can use to connect with Postgres. The volumes directive maps a directory on the host to a matching directory within the container, so that if the container is stopped or killed, we haven't lost the data.expose
directive allows other containers access on port 5432, but does not allow access outside of the Docker network. This is an important distinction for security purposes. We could also use the ports
directive, which would allow access to 5432 from the Internet. This can be helpful if you want to connect remotely, but at that point your Postgres password is the only thing preventing the entire world from gaining access.env_file
tells Compose where to look for environment variables. These variables are then passed into the container. The Postgres image has just one required environment variable – POSTGRES_PASSWORD
that must be defined, but we'll define a few others as well.POSTGRES_USER
POSTGRES_PASSWORD
POSTGRES_HOST
POSTGRES_DB
variables.env
, each variable takes its value from the host environment. You can also hard code values inside the config file, but it's better to keep them out of source control, especially with values such as passwords or API keys.psql
command-line program. First, find the ID of the Postgres container using docker ps
, and then we'll connect locally using docker exec
.docker exec -it ba52 psql -U pguser -d openranktracker
sudo apt-get install -y certbot
sudo certbot certonly --standalone --preferred-challenges http -d openranktracker.com
docker-compose.prod.yml
file, we only need to specify what is different, and those sections will take precedence.version: '3'
services:
nginx:
image: nginx
restart: always
volumes:
- /etc/letsencrypt:/etc/letsencrypt
- ./nginx.prod.conf:/etc/nginx/nginx.conf
- ./static:/static
- sock:/sock
ports:
- "443:443"
- "80:80"
nginx.prod.conf
makes use of the certificate to serve the application over HTTPS.nginx.prod.conf
file to see how SSL is handled.worker_processes 4;
events { worker_connections 1024; }
http {
include /etc/nginx/mime.types;
server {
listen 80;
listen [::]:80;
server_name _;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl default_server;
ssl_certificate /etc/letsencrypt/live/openranktracker.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/openranktracker.com/privkey.pem;
location / {
root /static;
try_files $uri $uri/ /index.html;
add_header Cache-Control "no-cache, public, must-revalidate, proxy-revalidate";
}
location /api {
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Host $host;
proxy_pass http://unix:/sock/app.sock:/api;
}
}
}
docker-compose -f docker-compose.yml -f docker-compose.prod.yml up -d