From e47ccaeb2622c62d811a36757e9a22da03e033ad Mon Sep 17 00:00:00 2001
From: Tim Neumann <neumantm@fius.informatik.uni-stuttgart.de>
Date: Wed, 10 Mar 2021 14:30:22 +0100
Subject: [PATCH] Add Dockerfile and dependencies

Also add some documentation on deploying with docker
---
 .docker/entrypoint.sh          |  37 ++++++++++
 .docker/extra_requirements.txt |   1 +
 .docker/uwsgi.ini              |   6 ++
 .dockerignore                  |  11 +++
 Dockerfile                     |  13 ++++
 INSTALL.md                     | 130 +++++++++++++++++++++++++++++++++
 6 files changed, 198 insertions(+)
 create mode 100644 .docker/entrypoint.sh
 create mode 100644 .docker/extra_requirements.txt
 create mode 100644 .docker/uwsgi.ini
 create mode 100644 .dockerignore
 create mode 100644 Dockerfile

diff --git a/.docker/entrypoint.sh b/.docker/entrypoint.sh
new file mode 100644
index 00000000..ecd068c6
--- /dev/null
+++ b/.docker/entrypoint.sh
@@ -0,0 +1,37 @@
+#!/bin/sh
+
+function wait_for_db()
+{
+  while ! ./manage.py sqlflush > /dev/null 2>&1 ;do
+    echo "Waiting for the db to be ready."
+    sleep 1
+  done
+}
+
+if [ "$SECRET_KEY" == "" ] ;then
+  echo "Need the environment variable SECRET_KEY."
+  exit 1
+fi
+
+echo "SECRET_KEY = '$SECRET_KEY'" >> ./AKPlanning/settings_secrets.py
+echo "HOSTS = $HOSTS" >> ./AKPlanning/settings_secrets.py
+echo "DB_NAME = '$DB_NAME'" >> ./AKPlanning/settings_secrets.py
+echo "DB_USER = '$DB_USER'" >> ./AKPlanning/settings_secrets.py
+echo "DB_PASSWORD = '$DB_PASSWORD'" >> ./AKPlanning/settings_secrets.py
+echo "DB_HOST = '$DB_HOST'" >> ./AKPlanning/settings_secrets.py
+
+if [ "$AUTO_MIGRATE_DB" == "true" ] ;then
+  wait_for_db
+  echo "Applying DB migrations"
+  ./manage.py migrate
+fi
+
+if [ "$DJANGO_SUPERUSER_PASSWORD" != "" ] ;then
+  wait_for_db
+  echo "Trying to create superuser."
+  ./manage.py createsuperuser --noinput
+fi
+
+./manage.py collectstatic --noinput
+./manage.py compilemessages -l de_DE
+uwsgi --ini .docker/uwsgi.ini
diff --git a/.docker/extra_requirements.txt b/.docker/extra_requirements.txt
new file mode 100644
index 00000000..b64ab47d
--- /dev/null
+++ b/.docker/extra_requirements.txt
@@ -0,0 +1 @@
+uwsgi==2.0.19.1
diff --git a/.docker/uwsgi.ini b/.docker/uwsgi.ini
new file mode 100644
index 00000000..7115f719
--- /dev/null
+++ b/.docker/uwsgi.ini
@@ -0,0 +1,6 @@
+[uwsgi]
+socket = 0.0.0.0:3035
+wsgi-file = AKPlanning/wsgi.py
+env = DJANGO_SETTINGS_MODULE=AKPlanning.settings_production
+processes = 4
+threads = 2
diff --git a/.dockerignore b/.dockerignore
new file mode 100644
index 00000000..ba3357ef
--- /dev/null
+++ b/.dockerignore
@@ -0,0 +1,11 @@
+apache-akplanning.conf
+CODE_OF_CONDUCT.md
+CONTRIBUTING.md
+CONTRIBUTORS.md
+.git
+.gitignore
+.gitlab-ci.yml
+INSTALL.md
+LICENSE.md
+README.md
+uwsgi-akplanning.ini
diff --git a/Dockerfile b/Dockerfile
new file mode 100644
index 00000000..8743d273
--- /dev/null
+++ b/Dockerfile
@@ -0,0 +1,13 @@
+FROM python:3-alpine
+
+RUN apk add --no-cache gcc python3-dev musl-dev libffi-dev mariadb-connector-c-dev gettext
+
+ADD . /app
+WORKDIR /app
+
+RUN pip install -r requirements.txt -r .docker/extra_requirements.txt
+
+ENV DJANGO_SETTINGS_MODULE=AKPlanning.settings_production
+
+EXPOSE 3035
+CMD ["sh", "/app/.docker/entrypoint.sh"]
diff --git a/INSTALL.md b/INSTALL.md
index 040a5aa8..c351d075 100644
--- a/INSTALL.md
+++ b/INSTALL.md
@@ -100,9 +100,139 @@ start uwsgi using the configuration file ``uwsgi --ini uwsgi-akplanning.ini``
 1. execute the update script ``./Utils/update.sh --prod``
 
 
+## Deployment Setup using Docker
+This project also provides a docker file for easy deployment.
+
+The container described by the docker file only contains the project itself.
+Additional containers for the database and webserver are needed to use it.
+
+The following [docker-compose](https://docs.docker.com/compose/) file shows a typical usage:
+```
+version: "3"
+
+networks:
+  akplanning:
+    external: false
+
+volumes:
+  static-files:
+
+services:
+  mariadb:
+    image: mariadb:10
+    restart: always
+    environment:
+      MYSQL_ROOT_PASSWORD: supermegasecrey
+      MYSQL_DATABASE: akplanning
+      MYSQL_USER: akplanning
+      MYSQL_PASSWORD: secret
+      TZ: Europe/Berlin
+    networks:
+      - akplanning
+
+  akplanning-server:
+    image: neumantm/akplanning:2021-03-10
+    restart: always
+    environment:
+      SECRET_KEY: superlongandsupersecret
+      DB_HOST: mariadb
+      DB_USER: akplanning
+      DB_NAME: akplanning
+      DB_PASSWORD: secret
+      HOSTS: "['akplanning.example.net', 'akplanning.example.de']"
+      TZ: Europe/Berlin
+      AUTO_MIGRATE_DB: 'true'
+      DJANGO_SUPERUSER_USERNAME: admin
+      DJANGO_SUPERUSER_EMAIL: admin@example.com
+      DJANGO_SUPERUSER_PASSWORD: supersecret
+    depends_on:
+      - mariadb
+    networks:
+      - akplanning
+    volumes:
+      - static-files:/app/static
+
+  web-server:
+    image: nginx
+    restart: always
+    volumes:
+      - /path/to/nginx.conf:/etc/nginx/nginx.conf:ro
+      - static-files:/var/www/ak-static
+    ports:
+      - "8080:80"
+    depends_on:
+      - ak-server
+    networks:
+      - akplanning
+```
+
+The `nginx.conf` would look like this:
+
+```
+user  nginx;
+worker_processes  1;
+error_log  /var/log/nginx/error.log warn;
+pid        /var/run/nginx.pid;
+
+events {
+    worker_connections  1024;
+}
+
+http {
+    include       /etc/nginx/mime.types;
+    default_type  application/octet-stream;
+    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
+                      '$status $body_bytes_sent "$http_referer" '
+                      '"$http_user_agent" "$http_x_forwarded_for"';
+    access_log  /var/log/nginx/access.log  main;
+    sendfile        on;
+    keepalive_timeout  65;
+    server {
+        listen      80;
+        server_name localhost:8080;
+
+        location /static/ {
+            alias /var/www/ak-static/;
+        }
+
+        location / {
+            include /etc/nginx/uwsgi_params;
+            uwsgi_pass uwsgi://ak-server:3035;
+        }
+    }
+}
+```
+### Initializing and migrating database
+On the first start, the database must be initialized (the Tables created and so on).
+When updating the project the database must be migrated.
+Both are done using the `migrate` command.
+
+This can be done manually by runningthe following command after the container has started:
+`docker-compose exec -it akplanning-server ./manage.py migrate`
+
+It can also be done automatically on each container start by setting `AUTO_MIGRATE_DB` to the string `true`
+(as shown in the docker-compose file above).
+
+Database migration may lead to the corruption or loss of data in some cases.
+Make sure you have a backup before running the command and be very careful with enabling auto migration.
+
+### Creating initial superuser
+There are two ways to create the initial superuser when using the docker container.
+For both the database must have been intialized before.
+
+The first way is already shown in the docker-compose file above:
+Using the environment variables `DJANGO_SUPERUSER_{USERNAME,EMAIL,PASSWORD}`.
+
+The second way is to run the following command after the container has started:
+`docker-compose exec -it akplanning-server ./manage.py createsuperuser`
+
 ## Updates
 
 To update the setup to the current version on the main branch of the repository use the update script ``Utils/update.sh`` or ``Utils/update.sh --prod`` in production.
 
 Afterwards, you may check your setup by executing ``Utils/check.sh`` or ``Utils/check.sh --prod`` in production.
 
+### Updating when using docker
+
+To update when using docker, just switch the tag of the image for `akplanning-server`.
+Then (if `AUTO_MIGRATE_DB` is not enabled), do a database migration as described in [Initializing and migrating database](#initializing-and-migrating-database)
-- 
GitLab