Skip to content

Commit c6a54de

Browse files
Merge pull request #87 from CentreForDigitalHumanities/feature/production-containers
Feature/production containers
2 parents 0ad5ca8 + 0538d9c commit c6a54de

24 files changed

Lines changed: 493 additions & 209 deletions

.env.example

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
POSTGRES_USER=
2+
POSTGRES_PASSWORD=
3+
POSTGRES_DB=
4+
DJANGO_SECRET_KEY=
5+
APP_VERSION=

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,3 +43,6 @@ frontend/src/environments/version.ts
4343
/backend/fracas.xml
4444
/backend/snli_1.0_dev.txt
4545
/backend/snli_1.0_test.txt
46+
47+
# Docker container logs
48+
logs/

CONTRIBUTING.md

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,20 +55,36 @@ If you are reading this document, you'll likely be working with the integrated p
5555

5656
### Quickstart with Docker
5757

58+
The application can be run in two modes: development mode and production mode. The former is meant for live development, while the latter is meant for simulating deployment conditions as closely as possible.
59+
60+
Begin by creating a `.env` file in the project root with the following contents:
61+
62+
```env
63+
POSTGRES_USER=my-user-name
64+
POSTGRES_PASSWORD=my-password
65+
POSTGRES_DB=langpro
66+
DJANGO_SECRET_KEY=my-secret-key
67+
APP_VERSION=1.0.0
68+
```
69+
70+
Then update the values after the `=` as needed. The `POSTGRES_*` variables are used to configure the PostgreSQL database. The `DJANGO_SECRET_KEY` variable is used to set the Django `SECRET_KEY` setting. The `APP_VERSION` variable is used to set the version of the application, which is displayed in the frontend.
71+
72+
Finally, run the command below to start the application in development mode.
73+
5874
```console
59-
docker compose up -d
75+
docker compose --profile dev up -d
6076
```
6177

6278
This will run the frontend and backend applications and watch all source files for changes. To run the backend unittests:
6379

6480
```console
65-
docker compose exec backend pytest
81+
docker compose exec backend-dev pytest
6682
```
6783

6884
To run the frontend unittests:
6985

7086
```console
71-
docker compose exec frontend yarn ng test --no-browsers
87+
docker compose exec frontend-dev yarn ng test --no-browsers
7288
```
7389

7490
then open http://localhost:9876 in a browser of choice.

backend/Dockerfile

Lines changed: 19 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,24 @@
1-
FROM python:3.11
1+
FROM python:3.11-slim-trixie
2+
3+
# Set environment variables.
4+
ENV PYTHONDONTWRITEBYTECODE=1
5+
ENV PYTHONUNBUFFERED=1
6+
7+
# Install dependencies.
8+
RUN apt update
9+
RUN pip install gunicorn
210

3-
WORKDIR /usr/src/app/backend
411
COPY requirements.txt .
5-
RUN pip install -U pip pip-tools && pip-sync
12+
RUN pip install -r requirements.txt --no-cache-dir
613

14+
# Set working directory.
15+
WORKDIR /usr/src/app
16+
17+
# Copy project files.
718
COPY . .
819

9-
CMD python manage.py check && \
10-
python manage.py migrate && \
11-
python manage.py runserver --settings glue --pythonpath .. 0.0.0.0:8000
20+
# Create a directory for Gunicorn logs (production).
21+
RUN mkdir -p /usr/src/app/logs
22+
23+
# Expose port.
24+
EXPOSE 8000
Lines changed: 110 additions & 107 deletions
Original file line numberDiff line numberDiff line change
@@ -1,107 +1,110 @@
1-
INSTALLED_APPS = [
2-
"django.contrib.admin",
3-
"django.contrib.auth",
4-
"django.contrib.contenttypes",
5-
"django.contrib.sessions",
6-
"django.contrib.messages",
7-
"livereload",
8-
"django.contrib.staticfiles",
9-
"rest_framework",
10-
"django.contrib.sites",
11-
"rest_framework.authtoken",
12-
"dj_rest_auth",
13-
"dj_rest_auth.registration",
14-
"allauth",
15-
"allauth.account",
16-
# Required for deleting accounts, but not actually used,
17-
# cf. https://github.com/iMerica/dj-rest-auth/pull/110.
18-
"allauth.socialaccount",
19-
"user",
20-
"revproxy",
21-
"problem",
22-
"annotation",
23-
]
24-
25-
MIDDLEWARE = [
26-
"django.middleware.security.SecurityMiddleware",
27-
"django.contrib.sessions.middleware.SessionMiddleware",
28-
"django.middleware.locale.LocaleMiddleware",
29-
"django.middleware.common.CommonMiddleware",
30-
"django.middleware.csrf.CsrfViewMiddleware",
31-
"django.contrib.auth.middleware.AuthenticationMiddleware",
32-
"django.contrib.messages.middleware.MessageMiddleware",
33-
"django.middleware.clickjacking.XFrameOptionsMiddleware",
34-
"allauth.account.middleware.AccountMiddleware",
35-
]
36-
37-
# Internationalization
38-
# https://docs.djangoproject.com/en/3.0/topics/i18n/
39-
LANGUAGES = [
40-
("en", "English"),
41-
("nl", "Nederlands"),
42-
]
43-
LANGUAGE_CODE = "en"
44-
45-
TIME_ZONE = "Europe/Amsterdam"
46-
47-
USE_I18N = True
48-
49-
USE_TZ = True # Authentication
50-
REST_FRAMEWORK = {
51-
"DEFAULT_AUTHENTICATION_CLASSES": [
52-
"rest_framework.authentication.TokenAuthentication",
53-
"rest_framework.authentication.SessionAuthentication",
54-
]
55-
}
56-
57-
AUTH_USER_MODEL = "user.User"
58-
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
59-
ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"]
60-
61-
SITE_ID = 1
62-
SITE_NAME = "langpro_annotator"
63-
64-
# Remove this setting in production!
65-
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
66-
67-
HOST = "localhost:8000"
68-
69-
REST_AUTH = {
70-
"USER_DETAILS_SERIALIZER": "user.serializers.CustomUserDetailsSerializer",
71-
}
72-
73-
LOGGING = {
74-
"version": 1,
75-
"disable_existing_loggers": False,
76-
"formatters": {
77-
"verbose": {
78-
"format": "{levelname} {asctime} {module} {message}",
79-
"style": "{",
80-
},
81-
"simple": {
82-
"format": "{levelname} {message}",
83-
"style": "{",
84-
},
85-
},
86-
"handlers": {
87-
"console": {
88-
"level": "INFO",
89-
"class": "logging.StreamHandler",
90-
"formatter": "simple",
91-
},
92-
},
93-
"loggers": {
94-
"django": {
95-
"handlers": ["console"],
96-
"level": "INFO",
97-
"propagate": False,
98-
},
99-
"LangProAnnotator": {
100-
"handlers": ["console"],
101-
"level": "INFO",
102-
"propagate": False,
103-
},
104-
},
105-
}
106-
107-
LANGPRO_URL = "http://localhost:8080"
1+
import os
2+
3+
4+
INSTALLED_APPS = [
5+
"django.contrib.admin",
6+
"django.contrib.auth",
7+
"django.contrib.contenttypes",
8+
"django.contrib.sessions",
9+
"django.contrib.messages",
10+
"livereload",
11+
"django.contrib.staticfiles",
12+
"rest_framework",
13+
"django.contrib.sites",
14+
"rest_framework.authtoken",
15+
"dj_rest_auth",
16+
"dj_rest_auth.registration",
17+
"allauth",
18+
"allauth.account",
19+
# Required for deleting accounts, but not actually used,
20+
# cf. https://github.com/iMerica/dj-rest-auth/pull/110.
21+
"allauth.socialaccount",
22+
"user",
23+
"revproxy",
24+
"problem",
25+
"annotation",
26+
]
27+
28+
MIDDLEWARE = [
29+
"django.middleware.security.SecurityMiddleware",
30+
"django.contrib.sessions.middleware.SessionMiddleware",
31+
"django.middleware.locale.LocaleMiddleware",
32+
"django.middleware.common.CommonMiddleware",
33+
"django.middleware.csrf.CsrfViewMiddleware",
34+
"django.contrib.auth.middleware.AuthenticationMiddleware",
35+
"django.contrib.messages.middleware.MessageMiddleware",
36+
"django.middleware.clickjacking.XFrameOptionsMiddleware",
37+
"allauth.account.middleware.AccountMiddleware",
38+
]
39+
40+
# Internationalization
41+
# https://docs.djangoproject.com/en/3.0/topics/i18n/
42+
LANGUAGES = [
43+
("en", "English"),
44+
("nl", "Nederlands"),
45+
]
46+
LANGUAGE_CODE = "en"
47+
48+
TIME_ZONE = "Europe/Amsterdam"
49+
50+
USE_I18N = True
51+
52+
USE_TZ = True # Authentication
53+
REST_FRAMEWORK = {
54+
"DEFAULT_AUTHENTICATION_CLASSES": [
55+
"rest_framework.authentication.TokenAuthentication",
56+
"rest_framework.authentication.SessionAuthentication",
57+
]
58+
}
59+
60+
AUTH_USER_MODEL = "user.User"
61+
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
62+
ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"]
63+
64+
SITE_ID = 1
65+
SITE_NAME = "langpro_annotator"
66+
67+
# Remove this setting in production!
68+
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"
69+
70+
HOST = "localhost:8000"
71+
72+
REST_AUTH = {
73+
"USER_DETAILS_SERIALIZER": "user.serializers.CustomUserDetailsSerializer",
74+
}
75+
76+
LOGGING = {
77+
"version": 1,
78+
"disable_existing_loggers": False,
79+
"formatters": {
80+
"verbose": {
81+
"format": "{levelname} {asctime} {module} {message}",
82+
"style": "{",
83+
},
84+
"simple": {
85+
"format": "{levelname} {message}",
86+
"style": "{",
87+
},
88+
},
89+
"handlers": {
90+
"console": {
91+
"level": "INFO",
92+
"class": "logging.StreamHandler",
93+
"formatter": "simple",
94+
},
95+
},
96+
"loggers": {
97+
"django": {
98+
"handlers": ["console"],
99+
"level": "INFO",
100+
"propagate": False,
101+
},
102+
"LangProAnnotator": {
103+
"handlers": ["console"],
104+
"level": "INFO",
105+
"propagate": False,
106+
},
107+
},
108+
}
109+
110+
LANGPRO_URL = os.environ.get('LANGPRO_CONTAINER') or 'http://localhost:8080'

backend/langpro_annotator/settings.py

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,22 @@
1919
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/
2020

2121
# SECURITY WARNING: keep the secret key used in production secret!
22-
SECRET_KEY = 'kxreeb3bds$oibo7ex#f3bi5r+d(1x5zljo-#ms=i2%ih-!pvn'
22+
SECRET_KEY = os.getenv("DJANGO_SECRET_KEY", "django-insecure-1234567890")
2323

2424
# SECURITY WARNING: don't run with debug turned on in production!
25-
DEBUG = True
25+
DEBUG = int(os.getenv("DJANGO_DEBUG", 0)) == 1
2626

27-
ALLOWED_HOSTS = ['localhost', '127.0.0.1']
27+
# CSRF trusted origins for cross-origin requests
28+
CSRF_TRUSTED_ORIGINS = []
2829

30+
ALLOWED_HOSTS = ["la-backend"]
31+
32+
if DEBUG:
33+
ALLOWED_HOSTS.append("localhost")
34+
CSRF_TRUSTED_ORIGINS.extend([
35+
"http://localhost:5000",
36+
"http://127.0.0.1:5000",
37+
])
2938

3039
# Application definition
3140

backend/langpro_annotator/urls.py

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@
2222
from rest_framework import routers
2323

2424
from annotation.views import LabelAnnotationView
25+
from langpro_annotator.views import csrf_token
2526
from problem.views.problem import ProblemView
2627

2728
from .index import index
@@ -33,10 +34,10 @@
3334
api_router.register(r"label", LabelAnnotationView, basename="labels")
3435

3536

36-
if settings.PROXY_FRONTEND:
37-
spa_url = re_path(r"^(?P<path>.*)$", proxy_frontend)
38-
else:
39-
spa_url = re_path(r"", index)
37+
# if settings.PROXY_FRONTEND:
38+
# spa_url = re_path(r"^(?P<path>.*)$", proxy_frontend)
39+
# else:
40+
# spa_url = re_path(r"", index)
4041

4142
urlpatterns = [
4243
path("admin", RedirectView.as_view(url="/admin/", permanent=True)),
@@ -53,6 +54,7 @@
5354
),
5455
),
5556
path("api/i18n/", i18n),
57+
path("api/csrf", csrf_token),
5658
path("users/", include("user.urls")),
57-
spa_url, # catch-all; unknown paths to be handled by a SPA
59+
# spa_url, # catch-all; unknown paths to be handled by a SPA
5860
]

backend/langpro_annotator/views.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
from django.views.decorators.csrf import ensure_csrf_cookie
2+
from django.http import JsonResponse
3+
4+
@ensure_csrf_cookie
5+
def csrf_token(request):
6+
return JsonResponse({"detail": "CSRF cookie set"})

backend/requirements.in

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ Django>=4.0.1,<5
22
djangorestframework
33
django-livereload-server
44
django-revproxy>=0.10.0
5-
psycopg2
5+
psycopg2-binary
66
pytest
77
pytest-django
88
pytest-xdist

backend/requirements.txt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ packaging==24.2
4848
# via pytest
4949
pluggy==1.5.0
5050
# via pytest
51-
psycopg2==2.9.10
51+
psycopg2-binary==2.9.10
5252
# via -r requirements.in
5353
pytest==8.3.5
5454
# via

0 commit comments

Comments
 (0)