Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Multiple build changes, add gunicorn, build nginx docker image for production #207

Merged
merged 13 commits into from
Dec 11, 2023
Merged
29 changes: 29 additions & 0 deletions .github/workflows/build-push.yml
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,24 @@ jobs:
type=semver,pattern={{major}}
type=sha

- name: Docker meta
id: meta_nginx
uses: docker/metadata-action@v5
with:
# list of Docker images to use as base name for tags
images: |
nauedu/nau-financial-manager-nginx
# ghcr.io/username/app
# generate Docker tags based on the following events/attributes
tags: |
type=schedule
type=ref,event=branch
type=ref,event=pr
type=semver,pattern={{version}}
type=semver,pattern={{major}}.{{minor}}
type=semver,pattern={{major}}
type=sha

# - name: Set up QEMU
# uses: docker/setup-qemu-action@v3

Expand All @@ -61,6 +79,17 @@ jobs:
with:
context: .
file: docker/Dockerfile
target: production # build the production docker stage
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta.outputs.tags }}
labels: ${{ steps.meta.outputs.labels }}

- name: Build and push
uses: docker/build-push-action@v5
with:
context: .
file: docker/Dockerfile
target: nginx # build the nginx docker stage
push: ${{ github.event_name != 'pull_request' }}
tags: ${{ steps.meta_nginx.outputs.tags }}
labels: ${{ steps.meta_nginx.outputs.labels }}
4 changes: 2 additions & 2 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -166,5 +166,5 @@ db.sqlite3

.docker-compose.yml

#default django storages directory to work with django.core.files.storage.FileSystemStorage
files
# default django storages directory to work with django.core.files.storage.FileSystemStorage
./files/
51 changes: 40 additions & 11 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,12 @@ POETRY = poetry
SRC_DIR = src
TEST_DIR = tests
POETRY_RUN = poetry run
DOCKER_COMPOSE = docker-compose
DOCKER_TARGET ?= development
ifeq ($(DOCKER_TARGET), development)
APP_DOCKER_COMMAND='./wait-for-mysql.sh python manage.py runserver 0.0.0.0:8000'
else
APP_DOCKER_COMMAND='gunicorn --workers 3 -c /usr/local/etc/gunicorn/app.py nau_financial_manager.wsgi:application'
endif
# or use in future the 'pytest' directly
TEST_CMD = $(POETRY_RUN) python manage.py test --settings=nau_financial_manager.test
# TEST_CMD = $(POETRY_RUN) pytest
Expand All @@ -18,12 +23,16 @@ RESET_MIGRATIONS = find . -path "*/migrations/*.py" -not -name "__init__.py" -de
MAKE_MIGRATIONS = $(POETRY_RUN) python manage.py makemigrations
MIGRATE = $(POETRY_RUN) python manage.py migrate
COMPOSE_FILE := docker/docker-compose-dependencies.yml
ifneq ($(APP), false)
RUN_APP ?= true
ifeq ($(RUN_APP), true)
COMPOSE_FILE := $(COMPOSE_FILE):docker/docker-compose-app.yml
endif
RUN_DOCKER_DEV = COMPOSE_FILE=$(COMPOSE_FILE) $(DOCKER_COMPOSE) up -d --remove-orphans
KILL_DOCKER_DEV = COMPOSE_FILE=$(COMPOSE_FILE) $(DOCKER_COMPOSE) down
BUILD_DOCKER_DEV = COMPOSE_FILE=$(COMPOSE_FILE) $(DOCKER_COMPOSE) build
DOCKER_COMPOSE = APP_DOCKER_COMMAND=$(APP_DOCKER_COMMAND) DOCKER_TARGET=$(DOCKER_TARGET) COMPOSE_FILE=$(COMPOSE_FILE) docker-compose
RUN_DOCKER_DEV = $(DOCKER_COMPOSE) up -d --remove-orphans
STOP_DOCKER_DEV = $(DOCKER_COMPOSE) down
KILL_DOCKER_DEV = $(DOCKER_COMPOSE) kill
BUILD_DOCKER_DEV = $(DOCKER_COMPOSE) build
LOGS_DOCKER_DEV = $(DOCKER_COMPOSE) logs
PRUNE_DOCKER = docker system prune -af
CREATESUPERUSER = $(POETRY_RUN) python manage.py add_superuser --no-input --settings=nau_financial_manager.settings

Expand All @@ -48,10 +57,16 @@ pre-commit: ## use pre-commit to check best practices
$(PRE_COMMIT)
.PHONY: pre-commit

run: ## run django server in your host
run: ## run DB's inside docker and run django server in your host
RUN_APP=false make run-docker
$(RUN_CMD)
.PHONY: run

run-app: ## run django server in your host
$(RUN_DOCKER_DEV)
$(RUN_CMD)
.PHONY: run-app

populate: ## populate the database initially with fake data
$(POPULATE_DB)
.PHONY: populate
Expand All @@ -76,20 +91,30 @@ superuser: ## create django super user admin (username and password are option p
@args="$(filter-out $@,$(MAKECMDGOALS))" && $(CREATESUPERUSER) $${args:+--username=$${args%% *} --password=$${args##* }}
.PHONY: superuser

kill: ## stop django server in your host
killall manage.py
.PHONY: kill

run-docker: ## run django server in docker in dev mode
$(BUILD_DOCKER_DEV)
$(RUN_DOCKER_DEV)
@echo "The should be running on http://localhost:8000"
ifeq ($(RUN_APP), true)
@echo "The app should be running on 8000 port and you can access it from nginx on http://localhost:8081"
endif
.PHONY: run-docker

stop-docker: ## stop docker containers
$(STOP_DOCKER_DEV)
.PHONY: stop

kill: ## stop django server in your host
killall manage.py || echo "Couldn't find the app to kill"
.PHONY: kill

kill-docker: ## stop django server in docker in dev mode
$(KILL_DOCKER_DEV)
.PHONY: kill-docker

stop: | stop-docker kill kill-docker ## stop everything
@echo "Everything stopped or killed"
.PHONY: stop

hr-docker: ## remake complete docker environment (destroy dockers, prune docker, create dockers, migrate, superuser, populate)
$(MAKE) kill-docker
$(PRUNE_DOCKER)
Expand All @@ -114,3 +139,7 @@ install-packages: ## Install project dependencies
@echo "Installing project dependencies..."
@$(POETRY) install
.PHONY: install-packages

logs: ## display docker app logs (follow mode)
@$(LOGS_DOCKER_DEV) -f nau-financial-app
.PHONY: logs
10 changes: 9 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ poetry shell

Start the app's dependencies services:
```bash
APP=false make run-docker
RUN_APP=false make run-docker
```

You have to install the app package dependencies and run the migrations.
Expand All @@ -93,6 +93,14 @@ To execute the application outside the docker:
make run
```

## Run for PROD

To start the application using the production mode, run:

```bash
DOCKER_TARGET=production make run-docker
```

## GENERATING AND USING TOKEN

To generate a token you should use one of this two commands:
Expand Down
93 changes: 82 additions & 11 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,9 +1,20 @@
#
# Docker image for the nau-financial-manager application.
FROM python:3.11-slim AS base_build
#

# Define the multi-stage arguments
# those need to be redeclared without default value after each stage (FROM).
ARG NGINX_IMAGE_NAME=nginx
ARG NGINX_IMAGE_TAG=1.20.1
ARG DOCKER_USER=nau-financial-manager
ARG STATIC_ROOT=/nau/nau-financial-manager/static

# ---- Base image ----
FROM python:3.11-slim AS core

# Create a group and user to run our app
ARG USER=nau-financial-manager

ARG DOCKER_USER
ARG ENV

ENV ENV=${ENV} \
Expand Down Expand Up @@ -33,6 +44,7 @@ RUN apt-get update && apt-get upgrade -y \
pkg-config \
default-libmysqlclient-dev \
mycli \
wait-for-it \
# Defining build-time-only dependencies:
$BUILD_ONLY_PACKAGES \
# Installing `poetry` package manager:
Expand All @@ -49,22 +61,81 @@ RUN mkdir /app
WORKDIR /app

# Setting up proper permissions:
RUN groupadd -r ${USER} && useradd --no-log-init -r -g ${USER} ${USER} \
&& chown ${USER}:${USER} -R /app
RUN groupadd -r ${DOCKER_USER} && \
useradd --no-log-init -r -g ${DOCKER_USER} ${DOCKER_USER} && \
chown ${DOCKER_USER}:${DOCKER_USER} -R /app

# Copy only requirements, to cache them in docker layer
COPY poetry.lock pyproject.toml /app/

# Install packages via poetry:
RUN poetry install --verbose

# Gunicorn
RUN mkdir -p /usr/local/etc/gunicorn
COPY ./docker/files/usr/local/etc/gunicorn/app.py /usr/local/etc/gunicorn/app.py

# Copy the default almost empty config.yml for production
COPY ./docker/files/app/config.yml /app/config.yml
ENV FINANCIAL_MANAGER_CFG=/app/config.yml

USER ${USER}:${USER}
# Copy the rest of the application files
COPY manage.py /app/
COPY nau_financial_manager /app/nau_financial_manager/
COPY wait-for-mysql.sh /app/
COPY apps /app/apps/

# ---- Static files/links collector ----
FROM core as collector

ARG STATIC_ROOT

# Install rdfind
RUN apt-get update && apt-get install -y rdfind

WORKDIR /app

# Collect static files
RUN SECRET_KEY=any python manage.py collectstatic --noinput

# Replace duplicated file by a symlink to decrease the overall size of the final image
RUN rdfind -makesymlinks true ${STATIC_ROOT}

# ---- Development image ----
FROM core as development

ARG DOCKER_USER

# Install development dependencies
# TODO

# Un-privileged user running the application
USER ${DOCKER_USER}:${DOCKER_USER}

# Run django development server
CMD python manage.py runserver 0.0.0.0:8000

# ---- Production image ----
FROM core as production

ARG DOCKER_USER

# Copy collected symlinks to static files
COPY --from=collector ${STATIC_ROOT} ${STATIC_ROOT}

# Un-privileged user running the application
USER ${DOCKER_USER}:${DOCKER_USER}

ENV GUNICORN_WORKERS=3

# The default command runs gunicorn WSGI server in the sandbox
CMD gunicorn --workers ${GUNICORN_WORKERS} -c /usr/local/etc/gunicorn/app.py nau_financial_manager.wsgi:application

# ---- Nginx ----
FROM ${NGINX_IMAGE_NAME}:${NGINX_IMAGE_TAG} as nginx

ARG STATIC_ROOT

# copy multiple files and folders to the /app folder inside the image
COPY manage.py \
wait-for-mysql.sh \
apps \
nau_financial_manager \
/app/
# copy static assets directly to web server image
RUN mkdir -p ${STATIC_ROOT}
COPY --from=collector ${STATIC_ROOT} ${STATIC_ROOT}
23 changes: 22 additions & 1 deletion docker/docker-compose-app.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,15 @@ services:

nau-financial-app:
container_name: nau-financial-app
command: sh -c './wait-for-mysql.sh && python manage.py runserver 0.0.0.0:8000'
command: ${APP_DOCKER_COMMAND}
build:
context: ../
dockerfile: docker/Dockerfile
target: ${DOCKER_TARGET}
# on docker mode change use the container directly
environment:
- DB_HOST=nau-database-mysql
- SECRET_KEY=a-different-secret-key
ports:
- 8000:8000
volumes:
Expand All @@ -19,3 +21,22 @@ services:
- database-mysql
- redis
- celery

# we just need nginx when we are usig the application from docker
nginx:
container_name: nau-financial-nginx
build:
context: ../
dockerfile: docker/Dockerfile
target: nginx
args:
NGINX_IMAGE_NAME: ${NGINX_IMAGE_NAME:-nginx}
NGINX_IMAGE_TAG: ${NGINX_IMAGE_TAG:-1.20.1}
STATIC_ROOT: ${STATIC_ROOT:-/nau/nau-financial-manager/static}
ports:
- "8081:8081"
volumes:
- ../docker/files/etc/nginx/conf.d:/etc/nginx/conf.d:ro
# - ../data/media:/data/media:ro
depends_on:
- nau-financial-app
3 changes: 3 additions & 0 deletions docker/docker-compose-dependencies.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ services:
build:
context: ../
dockerfile: docker/Dockerfile
target: ${DOCKER_TARGET}
command: celery -A nau_financial_manager worker --loglevel=info
volumes:
- ../:/app
Expand All @@ -37,6 +38,7 @@ services:
build:
context: ../
dockerfile: docker/Dockerfile
target: ${DOCKER_TARGET}
command: celery -A nau_financial_manager beat --loglevel=info --scheduler django_celery_beat.schedulers:DatabaseScheduler
environment:
- DB_HOST=nau-database-mysql
Expand All @@ -50,6 +52,7 @@ services:
build:
context: ../
dockerfile: docker/Dockerfile
target: ${DOCKER_TARGET}
command: celery -A nau_financial_manager flower --port=5555
volumes:
- ../:/app
Expand Down
8 changes: 8 additions & 0 deletions docker/files/app/config.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# Configuration for PRODUCTION!
#
# This is default configuration settings that will be put inside the docker container
# used in production.
# The operator should override this file using a docker volume or using a docker config.

# Currently we are just enforcing the debug.
DEBUG: False
28 changes: 28 additions & 0 deletions docker/files/etc/nginx/conf.d/default.conf
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
server {

listen 8081;
server_name localhost;
charset utf-8;

location ~ ^/static/(?P<file>.*) {
root /nau/nau-financial-manager/static;
try_files /$file @proxy_app;
}

location /media {
alias /data/media;
}

location / {
try_files /no-existing-directory @proxy_app;
}

location @proxy_app {
proxy_pass http://nau-financial-app: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;
}

}

Loading
Loading