Skip to content
Snippets Groups Projects
Commit 50e42deb authored by dcz2's avatar dcz2
Browse files

Dockerize the project and load settings from environment variables

parent dea7b8c7
Branches
Tags
No related merge requests found
UWSGI_PROCESS_PER_CONTAINER=4
DEBUG=true
ALLOWED_HOSTS=localhost,127.0.0.1
SECRET_KEY=Ixosoh1iemoh0Heloh1thee5akooboonu5veehae4aikoh2ohg
DATABASE_HOST=shellvalier-postgresql
DATABASE_NAME=shellvalier
DATABASE_USER=shellvalier
DATABASE_PASSWORD=shellvalier
DATABASE_PORT=5432
*.pyc *.pyc
*/__pycache__/* */__pycache__/*
*/migrations/*_auto_*.py */migrations/*_auto_*.py
/.datastore/
/.idea/
FROM ubuntu:focal
MAINTAINER Dariusz Czerski <dcz@ipipan.waw.pl>
ENV DEBIAN_FRONTEND=noninteractive \
LANG=en_US.UTF-8 \
LC_ALL=en_US.UTF-8 \
LANGUAGE=en_US:en \
TZ=Europe/Warsaw \
PYTHONUNBUFFERED=1 \
PYTHONFAULTHANDLER=1
ENV PACKAGES="\
binutils \
curl \
gdal-bin \
gettext \
git \
libproj-dev \
locales \
nginx \
postgresql-client \
python3-pip \
python3-setuptools \
python3-wheel \
python3.8 \
python3.8-dev \
syslinux \
tar \
tzdata \
unzip \
wget \
"
ENV DEV_PACKAGES="\
gpg-agent \
libcurl4-openssl-dev \
libssl-dev \
software-properties-common \
"
ADD requirements.txt /requirements.txt
RUN echo $TZ > /etc/timezone && \
ln -snf /usr/share/zoneinfo/$TZ /etc/localtime && \
apt update && \
apt install -y -f --no-install-recommends $PACKAGES $DEV_PACKAGES && \
update-alternatives --install /usr/bin/python python /usr/bin/python3.8 1 && \
update-alternatives --install /usr/bin/pip pip /usr/bin/pip3 1 && \
sed -i -e 's/# en_US.UTF-8 UTF-8/en_US.UTF-8 UTF-8/' /etc/locale.gen && \
echo "LC_ALL=$LC_ALL" >> /etc/environment && \
echo "en_US.UTF-8 UTF-8" >> /etc/locale.gen && \
echo "LANG=$LANG" > /etc/locale.conf && \
locale-gen $LANG && \
pip install -r /requirements.txt && \
wget -O - http://download.sgjp.pl/apt/sgjp.gpg.key | apt-key add - && \
apt-add-repository http://download.sgjp.pl/apt/ubuntu && \
apt update && \
apt install -y -f --no-install-recommends morfeusz2 && \
wget http://download.sgjp.pl/morfeusz/20211017/Linux/20.04/64/morfeusz2-1.99.1-20211017-cp35.cp36.cp37.cp38.cp39-abi3-linux_x86_64.whl && \
pip install morfeusz2-1.99.1-20211017-cp35.cp36.cp37.cp38.cp39-abi3-linux_x86_64.whl && \
rm morfeusz2-1.99.1-20211017-cp35.cp36.cp37.cp38.cp39-abi3-linux_x86_64.whl && \
mkdir /run/nginx/ && \
apt purge $DEV_PACKAGES -y && \
apt autoremove --purge -y && \
rm -rf /root/.cache/ && \
rm -rf /usr/src/ && \
rm -rf /var/lib/apt/lists/*
ADD docker/config/uwsgi.ini /uwsgi.ini
ADD . /app
VOLUME /app
WORKDIR /app
ENTRYPOINT ["/app/docker/scripts/docker-entrypoint"]
# ShellValier v2
## Running the development environment
In order to run the development environment locally:
1. Make sure you have [Docker Desktop](https://docs.docker.com/desktop/) and [Docker Compose](https://docs.docker.com/compose/) installed.
2. Build and run the project by executing:
./docker/scripts/run-docker
## Working in the development environment
Whenever you need to establish an interactive bash session in the running applicaiton container, execute:
./docker/scripts/docker-bash
version: "3"
networks:
app-tier:
driver: bridge
services:
postgresql:
container_name: shellvalier-postgresql
image: postgres:14.2
environment:
POSTGRES_USER: "shellvalier"
POSTGRES_DB: "shellvalier"
POSTGRES_PASSWORD: "shellvalier"
POSTGRES_INITDB_ARGS: "--encoding=UTF-8 --lc-collate=en_US.UTF-8 --lc-ctype=en_US.UTF-8"
networks:
- app-tier
volumes:
- ./.datastore/postgresql:/var/lib/postgresql/data
backend:
container_name: shellvalier-backend
image: shellvalier-developer:latest
depends_on:
- postgresql
networks:
- app-tier
env_file: .environment-dev
volumes:
- .:/app
ports:
- "8000:8000"
[uwsgi]
chdir = /app
wsgi-file = /app/shellvalier/wsgi.py
socket = /run/uwsgi.sock
pidfile = /run/uwsgi.pid
chmod-socket = 666
master = true
processes = UWSGI_PROCESS_PER_CONTAINER
enable-threads = true
lazy-apps = true
max-requests = 500
max-requests-delta = 10
buffer-size = 32768
harakiri = 300
disable-write-exception = true
#!/usr/bin/env bash
docker-compose exec backend bash
#!/bin/bash
echo 'Starting docker container'
set -e
function assertPresence() {
VARIABLE_NAME=$1
if [ -z ${!VARIABLE_NAME} ]; then
echo "${VARIABLE_NAME} is unset. Please set this label to run this docker container";
exit 1
fi
}
assertPresence UWSGI_PROCESS_PER_CONTAINER
sed -i.bak "s/UWSGI_PROCESS_PER_CONTAINER/$UWSGI_PROCESS_PER_CONTAINER/" /uwsgi.ini
exec "$@"
python manage.py compilemessages
/app/docker/scripts/postgres-alive
python manage.py migrate --noinput
exec python manage.py runserver 0:8000
#!/usr/bin/env bash
function assertPresence() {
VARIABLE_NAME=$1
if [ -z ${!VARIABLE_NAME} ]; then
echo "${VARIABLE_NAME} is unset. Please set this label to run this docker container";
exit 1
fi
}
assertPresence DATABASE_HOST
assertPresence DATABASE_USER
assertPresence DATABASE_PASSWORD
assertPresence DATABASE_PORT
STATE=NOT_RUN
WAIT=1
is_alive() {
WAIT=$((WAIT+1))
if [[ $WAIT -le 180 ]]; then
DB_CON_OK=$(PGPASSWORD=$DATABASE_PASSWORD psql -h $DATABASE_HOST -U $DATABASE_USER -p $DATABASE_PORT -t -A -c "SELECT datname FROM pg_database WHERE datname='$DATABASE_NAME'" $DATABASE_NAME | grep -i $DATABASE_NAME)
if [[ $DB_CON_OK == "$DATABASE_NAME" ]]; then
STATE=RUN
fi
else
exit
fi
}
while [ $STATE == "NOT_RUN" ]; do
is_alive
sleep 1
done
#!/usr/bin/env bash
set -e
docker build -t shellvalier-developer -f Dockerfile .
docker-compose up
...@@ -6,6 +6,7 @@ certifi==2020.6.20 ...@@ -6,6 +6,7 @@ certifi==2020.6.20
Django==3.1.5 Django==3.1.5
django-crispy-forms==1.10.0 django-crispy-forms==1.10.0
django-delayed-union==0.1.4 django-delayed-union==0.1.4
django-extensions==3.1.5
future==0.18.2 future==0.18.2
infinity==1.4 infinity==1.4
Jinja2==2.10.3 Jinja2==2.10.3
......
"""Utils for accessing process environment variables"""
import os
from typing import (
Any,
Callable,
List,
Optional,
)
from django.core.exceptions import ImproperlyConfigured
NOT_SET = object()
def get_environment(name: str, mapper: Optional[Callable[[str], Any]] = None, default=NOT_SET) -> Any:
"""Return a value with the given name from the process environment variables"""
possible_environ_names = [name]
for environ_name in possible_environ_names:
try:
value = os.environ[environ_name]
except KeyError:
continue
else:
break
else:
if default is NOT_SET:
raise ImproperlyConfigured('The {} environment variable is not present under names: {}'.format(name, ', '.join(possible_environ_names)))
else:
return default
mapper = mapper or str
return mapper(value)
def boolean_mapper(value: str) -> bool:
"""Map a string representation on a boolean value to Python bool"""
if value.lower() in {'true', '1', 'yes'}:
return True
if value.lower() in {'false', '0', 'no'}:
return False
raise ImproperlyConfigured('Boolean mapper cannot map the {} value to bool'.format(value))
def list_mapper_factory(delimiter: Optional[str] = None, item_mapper: Optional[Callable[[str], Any]] = None) -> Callable[[str], List[Any]]:
"""Return a function that maps lists serialized to a string with a specified delimiter to a Python list
:param delimiter: the item delimiter in the serialized list
:param item_mapper: the mapper for list elements
:return: the mapper function
"""
delimiter = delimiter or ','
item_mapper = item_mapper or str
def list_mapper(value: str) -> List[Any]:
"""Map a list serialized to a string to a Python list
Assumes an empty string represents an empty list, not a list containing an empty string.
:param value: the list serialized to string
:return: the parsed list
"""
if value == '':
return []
return [item_mapper(item) for item in value.split(delimiter)]
return list_mapper
...@@ -12,6 +12,9 @@ https://docs.djangoproject.com/en/2.1/ref/settings/ ...@@ -12,6 +12,9 @@ https://docs.djangoproject.com/en/2.1/ref/settings/
import os import os
from .environment import get_environment, boolean_mapper, list_mapper_factory
# Build paths inside the project like this: os.path.join(BASE_DIR, ...) # Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
...@@ -20,10 +23,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) ...@@ -20,10 +23,10 @@ BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
# See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/ # See https://docs.djangoproject.com/en/2.1/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret! # SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = 'xxx' SECRET_KEY = get_environment('SECRET_KEY')
# SECURITY WARNING: don't run with debug turned on in production! # SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True DEBUG = get_environment('DEBUG', mapper=boolean_mapper)
# for production # for production
''' '''
...@@ -34,7 +37,7 @@ CSRF_COOKIE_SECURE = True ...@@ -34,7 +37,7 @@ CSRF_COOKIE_SECURE = True
X_FRAME_OPTIONS = 'DENY' X_FRAME_OPTIONS = 'DENY'
''' '''
ALLOWED_HOSTS = ['127.0.0.1'] ALLOWED_HOSTS = get_environment('ALLOWED_HOSTS', mapper=list_mapper_factory())
# Application definition # Application definition
...@@ -56,6 +59,7 @@ INSTALLED_APPS = [ ...@@ -56,6 +59,7 @@ INSTALLED_APPS = [
'dictionary_statistics.apps.DictionaryStatisticsConfig', 'dictionary_statistics.apps.DictionaryStatisticsConfig',
'download.apps.DownloadConfig', 'download.apps.DownloadConfig',
'crispy_forms', 'crispy_forms',
'django_extensions',
] ]
CRISPY_TEMPLATE_PACK = 'bootstrap4' CRISPY_TEMPLATE_PACK = 'bootstrap4'
...@@ -99,11 +103,11 @@ WSGI_APPLICATION = 'shellvalier.wsgi.application' ...@@ -99,11 +103,11 @@ WSGI_APPLICATION = 'shellvalier.wsgi.application'
DATABASES = { DATABASES = {
'default': { 'default': {
'ENGINE': 'django.db.backends.postgresql', 'ENGINE': 'django.db.backends.postgresql',
'NAME': 'shellvalier', 'NAME': get_environment('DATABASE_NAME'),
'USER': 'shellvalier', 'USER': get_environment('DATABASE_USER'),
'PASSWORD': 'shellvalier', 'PASSWORD': get_environment('DATABASE_PASSWORD'),
'HOST': 'localhost', 'HOST': get_environment('DATABASE_HOST'),
'PORT': '', 'PORT': get_environment('DATABASE_PORT', mapper=int),
}} }}
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment