Flask-Postgresql-Gunicorn-Nginx Example¶
Project¶
-
env
-
.env.prod
2. .env.prod.dbFLASK_APP=project/__init__.py FLASK_ENV=production DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_prod SQL_HOST=db SQL_PORT=5432 DATABASE=postgres
POSTGRES_USER=hello_flask POSTGRES_PASSWORD=hello_flask POSTGRES_DB=hello_flask_prod
-
docker-compose
version: '3.8'
services:
web:
build: ./services/web
command: python manage.py run -h 0.0.0.0
volumes:
- ./services/web/:/usr/src/app/
ports:
- 5000:5000
env_file:
- ./.env.dev
depends_on:
- db
db:
image: postgres:13-alpine
volumes:
- postgres_data:/var/lib/postgresql/data/
environment:
- POSTGRES_USER=hello_flask
- POSTGRES_PASSWORD=hello_flask
- POSTGRES_DB=hello_flask_dev
volumes:
postgres_data:
docker-compose build
docker-compose up -d
- dockerfile
# pull official base image
FROM python:3.9.5-slim-buster
# set work directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install system dependencies
RUN apt-get update && apt-get install -y netcat
# install dependencies
RUN pip install --upgrade pip
COPY ./requirements.txt /usr/src/app/requirements.txt
RUN pip install -r requirements.txt
# copy project
COPY . /usr/src/app/
# run entrypoint.sh
ENTRYPOINT ["/usr/src/app/entrypoint.sh"]
- entrypoint
entrypoint.prod.sh
#!/bin/sh if [ "$DATABASE" = "postgres" ] then echo "Waiting for postgres..." while ! nc -z $SQL_HOST $SQL_PORT; do sleep 0.1 done echo "PostgreSQL started" fi if [ "$FLASK_ENV" = "development" ] then echo "Creating the database tables..." python manage.py create_db echo "Tables created" fi exec "$@"
- python
__init__.py
from flask import Flask, jsonify
from flask_sqlalchemy import SQLAlchemy
app = Flask(__name__)
app.config.from_object("project.config.Config")
db = SQLAlchemy(app)
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String(128), unique=True, nullable=False)
active = db.Column(db.Boolean(), default=True, nullable=False)
def __init__(self, email):
self.email = email
@app.route("/")
def hello_world():
return jsonify(hello="world")
manage.py
from flask.cli import FlaskGroup from project import app, db, User cli = FlaskGroup(app) @cli.command("create_db") def create_db(): db.drop_all() db.create_all() db.session.commit() @cli.command("seed_db") def seed_db(): db.session.add(User(email="michael@mherman.org")) db.session.commit() if __name__ == "__main__": cli()
- command
docker-compose up -d --build
# create table
# docker-compose down -v
docker-compose exec web python manage.py create_db
docker-compose exec db psql --username=hello_flask --dbname=hello_flask_dev
docker volume inspect flask-on-docker_postgres_data
$ docker build -f ./services/web/Dockerfile -t hello_flask:latest ./services/web
- gunicorn
version: '3.8'
services:
web:
build: ./services/web
command: gunicorn --bind 0.0.0.0:5000 manage:app
ports:
- 5000:5000
env_file:
- ./.env.prod
depends_on:
- db
db:
image: postgres:13-alpine
volumes:
- postgres_data_prod:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
volumes:
postgres_data_prod:
bash
docker-compose down -v
docker-compose -f docker-compose.prod.yml up -d --build
- prod dockerfile
###########
# BUILDER #
###########
# pull official base image
FROM python:3.9.5-slim-buster as builder
# set work directory
WORKDIR /usr/src/app
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc
# lint
RUN pip install --upgrade pip
RUN pip install flake8==3.9.1
COPY . /usr/src/app/
RUN flake8 --ignore=E501,F401 .
# install python dependencies
COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/app/wheels -r requirements.txt
#########
# FINAL #
#########
# pull official base image
FROM python:3.9.5-slim-buster
# create directory for the app user
RUN mkdir -p /home/app
# create the app user
RUN addgroup --system app && adduser --system --group app
# create the appropriate directories
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
WORKDIR $APP_HOME
# install dependencies
RUN apt-get update && apt-get install -y --no-install-recommends netcat
COPY --from=builder /usr/src/app/wheels /wheels
COPY --from=builder /usr/src/app/requirements.txt .
RUN pip install --upgrade pip
RUN pip install --no-cache /wheels/*
# copy entrypoint-prod.sh
COPY ./entrypoint.prod.sh $APP_HOME
# copy project
COPY . $APP_HOME
# chown all the files to the app user
RUN chown -R app:app $APP_HOME
# change to the app user
USER app
# run entrypoint.prod.sh
ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"]
- update dockerfile.prod
web:
build:
context: ./services/web
dockerfile: Dockerfile.prod
command: gunicorn --bind 0.0.0.0:5000 manage:app
ports:
- 5000:5000
env_file:
- ./.env.prod
depends_on:
- db
- try it
$ docker-compose -f docker-compose.prod.yml down -v
$ docker-compose -f docker-compose.prod.yml up -d --build
$ docker-compose -f docker-compose.prod.yml exec web python manage.py create_db
-
nginx
-
structure
└── nginx ├── Dockerfile └── nginx.conf
-
docker-compose.prod.yml
nginx: build: ./services/nginx ports: - 1337:80 depends_on: - web
-
dockerfile
FROM nginx:1.19-alpine RUN rm /etc/nginx/conf.d/default.conf COPY nginx.conf /etc/nginx/conf.d
-
nginx.conf
upstream hello_flask { server web:5000; } server { listen 80; location / { proxy_pass http://hello_flask; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header Host $host; proxy_redirect off; } }
-
web
web: build: context: ./services/web dockerfile: Dockerfile.prod command: gunicorn --bind 0.0.0.0:5000 manage:app expose: - 5000 env_file: - ./.env.prod depends_on: - db
-
try
$ docker-compose -f docker-compose.prod.yml down -v $ docker-compose -f docker-compose.prod.yml up -d --build $ docker-compose -f docker-compose.prod.yml exec web python manage.py create_db
structure¶
├── .env.dev
├── .env.prod
├── .env.prod.db
├── .gitignore
├── docker-compose.prod.yml
├── docker-compose.yml
└── services
├── nginx
│ ├── Dockerfile
│ └── nginx.conf
└── web
├── Dockerfile
├── Dockerfile.prod
├── entrypoint.prod.sh
├── entrypoint.sh
├── manage.py
├── project
│ ├── __init__.py
│ └── config.py
└── requirements.txt
bash
docker-compose -f docker-compose.prod.yml down -v
final¶
- docker-compose
version: '3.8'
services:
web:
build:
context: ./services/web
dockerfile: Dockerfile.prod
command: gunicorn --bind 0.0.0.0:5000 manage:app
volumes:
- static_volume:/home/app/web/project/static
expose:
- 5000
env_file:
- ./.env.prod
depends_on:
- db
db:
image: postgres:13-alpine
volumes:
- postgres_data_prod:/var/lib/postgresql/data/
env_file:
- ./.env.prod.db
nginx:
build: ./services/nginx
volumes:
- static_volume:/home/app/web/project/static
ports:
- 1337:80
depends_on:
- web
volumes:
postgres_data_prod:
static_volume:
- nginx conf
upstream hello_flask {
server web:5000;
}
server {
listen 80;
location / {
proxy_pass http://hello_flask;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
}
location /static/ {
alias /home/app/web/project/static/;
}
}
.env
FLASK_APP=project/__init__.py
FLASK_ENV=production
DATABASE_URL=postgresql://hello_flask:hello_flask@db:5432/hello_flask_prod
SQL_HOST=db
SQL_PORT=5432
DATABASE=postgres
APP_FOLDER=/home/app/web
- test
docker-compose down -v
docker-compose -f docker-compose.prod.yml up -d --build
# down
docker-compose -f docker-compose.prod.yml down -v
Reference¶
[1]. https://testdriven.io/blog/dockerizing-flask-with-postgres-gunicorn-and-nginx/
[2]. https://github.com/testdrivenio/flask-on-docker