From 4a18d6214dca2db54596904ba2e9221520229a1e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tom=C3=A1=C5=A1=20Valenta?= <git@imaniti.org> Date: Wed, 4 Jan 2023 22:59:53 +0100 Subject: [PATCH] docker integration, CI --- .dockerignore | 3 ++ .gitlab-ci.yml | 30 ++++++++++++++++++ Dockerfile | 25 +++++++++++++++ Dockerfile.nginx | 3 ++ Makefile | 4 +-- README.md | 2 +- env.example | 3 ++ gunicorn.conf.py | 7 ++++ requirements/base.txt | 2 +- requirements/prod.txt | 2 ++ run.sh | 10 ++++++ rybicka/settings/base.py | 15 ++++----- rybicka/settings/prod.py | 12 +++++++ rybicka/wsgi.py | 2 +- .../static/shared}/calculator.webp | Bin shared/templates/shared/index.html | 2 +- 16 files changed, 108 insertions(+), 14 deletions(-) create mode 100644 .dockerignore create mode 100644 .gitlab-ci.yml create mode 100644 Dockerfile create mode 100644 Dockerfile.nginx create mode 100644 gunicorn.conf.py create mode 100644 requirements/prod.txt create mode 100644 run.sh create mode 100644 rybicka/settings/prod.py rename {member_group_size_calc/static/member_group_size_calc => shared/static/shared}/calculator.webp (100%) diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 0000000..fc98bf2 --- /dev/null +++ b/.dockerignore @@ -0,0 +1,3 @@ +.git +.venv +staticfiles/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000..cae401b --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,30 @@ +stages: + - build + +image: docker:20.10.8 + +variables: + DOCKER_TLS_CERTDIR: "/certs" + IMAGE_TAG_APP: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG + IMAGE_TAG_NGINX: $CI_REGISTRY_IMAGE:$CI_COMMIT_REF_SLUG-nginx + +services: + - docker:20.10.8-dind + +before_script: + - docker login -u $CI_REGISTRY_USER -p $CI_REGISTRY_PASSWORD $CI_REGISTRY + +build_app: + stage: build + script: + - docker pull $CI_REGISTRY_IMAGE:test || true + - docker build --cache-from $CI_REGISTRY_IMAGE:test -t $IMAGE_TAG_APP . + - docker push $IMAGE_TAG_APP + +build_nginx: + stage: build + when: manual + script: + - docker pull $CI_REGISTRY_IMAGE:test-nginx || true + - docker build --cache-from $CI_REGISTRY_IMAGE:test-nginx -t $IMAGE_TAG_NGINX . -f Dockerfile.nginx + - docker push $IMAGE_TAG_NGINX diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..4136651 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,25 @@ +FROM python:3.10 + +RUN mkdir /app +WORKDIR /app + +RUN curl -fsSL https://deb.nodesource.com/setup_19.x | bash - +RUN apt-get install nodejs && rm -rf /var/lib/apt/lists/* + +COPY . . + +RUN pip install -r requirements/base.txt -r requirements/prod.txt +RUN npm install +RUN npm run build +RUN python manage.py collectstatic --noinput --settings=rybicka.settings.prod + +RUN bash -c "adduser --disabled-login --quiet --gecos app app && \ + chmod -R o+r /app/ && \ + chmod o+x /app/run.sh" +USER app + +ENV DJANGO_SETTINGS_MODULE "rybicka.settings.prod" + +EXPOSE 8000 + +CMD ["bash", "run.sh"] diff --git a/Dockerfile.nginx b/Dockerfile.nginx new file mode 100644 index 0000000..4759154 --- /dev/null +++ b/Dockerfile.nginx @@ -0,0 +1,3 @@ +FROM nginx:1.18 +EXPOSE 8080 +ADD nginx.conf /etc/nginx/conf.d/rybicka.conf diff --git a/Makefile b/Makefile index 0b53584..4dbde64 100644 --- a/Makefile +++ b/Makefile @@ -3,7 +3,7 @@ PYTHON = python VENV = .venv PORT = 8012 -SETTINGS = rybicka.settings.dev +SETTINGS = rybicka.settings.prod .PHONY: help venv install build run shell migrations migrate @@ -26,7 +26,7 @@ venv: .venv/bin/python ${PYTHON} -m venv ${VENV} install: venv - ${VENV}/bin/pip install -r requirements/base.txt + ${VENV}/bin/pip install -r requirements/base.txt -r requirements/prod.txt ${VENV}/bin/nodeenv --python-virtualenv --node=19.3.0 ${VENV}/bin/npm install diff --git a/README.md b/README.md index 68e8f3c..8e019db 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Aplikace pro všemožné jednoduché nástroje, které pomohou automatizovat složité úkony. -## Setup +## Lokální setup Požadavky: - Python 3.9+ diff --git a/env.example b/env.example index f1dd3ea..5b8cb36 100644 --- a/env.example +++ b/env.example @@ -1,3 +1,6 @@ DATABASE_URL="postgresql://rybicka:rybicka@localhost:5432/rybicka" SECRET_KEY="%@=^sip3=tqn6d_-xvvidc1@-t0t3&*kab@vr4c4" + +# Production settings +ALLOWED_HOSTS="nastroje.pirati.cz" diff --git a/gunicorn.conf.py b/gunicorn.conf.py new file mode 100644 index 0000000..16484fd --- /dev/null +++ b/gunicorn.conf.py @@ -0,0 +1,7 @@ +bind = "0.0.0.0:8000" +accesslog = "-" +workers = 1 +max_requests = 1000 +max_requests_jitter = 10 +timeout = 60 +graceful_timeout = 60 diff --git a/requirements/base.txt b/requirements/base.txt index ca71420..150dea4 100644 --- a/requirements/base.txt +++ b/requirements/base.txt @@ -1,6 +1,6 @@ Django==4.1.5 django-database-url==1.0.3 -python-dotenv==0.21.0 +django-environ==0.9.0 psycopg2-binary==2.9.5 django-webpack-loader==1.8.0 nodeenv==1.7.0 diff --git a/requirements/prod.txt b/requirements/prod.txt new file mode 100644 index 0000000..9655c9a --- /dev/null +++ b/requirements/prod.txt @@ -0,0 +1,2 @@ +gunicorn==20.1.0 +whitenoise==6.3.0 diff --git a/run.sh b/run.sh new file mode 100644 index 0000000..5d9a7ad --- /dev/null +++ b/run.sh @@ -0,0 +1,10 @@ +#!/bin/bash + +# exit on error +set -e + +# migrate database +python manage.py migrate + +# start webserver +exec gunicorn -c gunicorn.conf.py rybicka.wsgi diff --git a/rybicka/settings/base.py b/rybicka/settings/base.py index 92b1822..8eb3f07 100644 --- a/rybicka/settings/base.py +++ b/rybicka/settings/base.py @@ -14,27 +14,26 @@ import os import pathlib import dj_database_url -import dotenv - -dotenv.load_dotenv() +import environ # Build paths inside the project like this: BASE_DIR / "subdir". BASE_DIR = pathlib.Path(__file__).parents[2] + +env = environ.Env() +environ.Env.read_env(os.path.join(BASE_DIR, ".env")) + # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/4.1/howto/deployment/checklist/ DEBUG = False # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = os.environ["SECRET_KEY"] +SECRET_KEY = env.str("SECRET_KEY") ALLOWED_HOSTS = [] -STATIC_ROOT = os.environ.get( - "STATIC_ROOT", - "staticfiles" -) +STATIC_ROOT = os.path.join(BASE_DIR, "staticfiles") # Application definition diff --git a/rybicka/settings/prod.py b/rybicka/settings/prod.py new file mode 100644 index 0000000..f3096fd --- /dev/null +++ b/rybicka/settings/prod.py @@ -0,0 +1,12 @@ +""" +Production settings. +""" + +import os + +from .base import * + +ALLOWED_HOSTS = env.list("ALLOWED_HOSTS") + +MIDDLEWARE.insert(1, "whitenoise.middleware.WhiteNoiseMiddleware") +STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage" diff --git a/rybicka/wsgi.py b/rybicka/wsgi.py index 8cfda5e..93e488c 100644 --- a/rybicka/wsgi.py +++ b/rybicka/wsgi.py @@ -11,6 +11,6 @@ import os from django.core.wsgi import get_wsgi_application -os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'rybicka.settings') +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "rybicka.settings.prod") application = get_wsgi_application() diff --git a/member_group_size_calc/static/member_group_size_calc/calculator.webp b/shared/static/shared/calculator.webp similarity index 100% rename from member_group_size_calc/static/member_group_size_calc/calculator.webp rename to shared/static/shared/calculator.webp diff --git a/shared/templates/shared/index.html b/shared/templates/shared/index.html index 4d19644..ff6c423 100644 --- a/shared/templates/shared/index.html +++ b/shared/templates/shared/index.html @@ -18,7 +18,7 @@ <article class="card"> <a href="{% url "member_group_size_calc_index" %}"> <img - src="{% static "member_group_size_calc/calculator.webp" %}" + src="{% static "shared/calculator.webp" %}" alt="Kalkulačka výpočtu skupiny členů" class="w-full h-48 object-cover" > -- GitLab