Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
f1a5a47
Split backend container into prod and dev
XanderVertegaal Mar 26, 2026
57fc4bc
Add container names to remaining containers
XanderVertegaal Mar 26, 2026
7c748d1
Add image names to backend services
XanderVertegaal Mar 26, 2026
60be35d
Add logs to .gitignore
XanderVertegaal Mar 26, 2026
952c439
Update proxy.conf.docker.json with new values
XanderVertegaal Mar 26, 2026
e511b3b
Update psycopg2 to use the binary package
XanderVertegaal Mar 26, 2026
45c1292
Add Nginx configuration
XanderVertegaal Mar 26, 2026
45c2f61
Install dependencies during build time
XanderVertegaal Mar 26, 2026
7007365
Only copy frontend folder to frontend container
XanderVertegaal Mar 27, 2026
00a810d
Update restart policy to unless-stopped on postgres container
XanderVertegaal Mar 27, 2026
360a576
Only mount backend folder to backend container
XanderVertegaal Mar 27, 2026
e5b0fc3
Move backend dev server start command to compose file
XanderVertegaal Mar 27, 2026
8673a59
Add production frontend container
XanderVertegaal Mar 27, 2026
c7f38aa
Do not create localized build (for now)
XanderVertegaal Mar 27, 2026
2e82d7b
Remove unused expose statements and env variables
XanderVertegaal Mar 27, 2026
b015206
Use .env variables in settings.py
XanderVertegaal Mar 27, 2026
253986b
Use appVersion in build-pre.js. Fail gracefully if git repo is not in…
XanderVertegaal Mar 27, 2026
fabcd30
Pass git info as args to prebuild script
XanderVertegaal Mar 27, 2026
9a42984
Add more mime types based on official Docker Angular containerization…
XanderVertegaal Mar 27, 2026
bc0ec89
Update CONTRIBUTING.md and provide example file for .env
XanderVertegaal Mar 27, 2026
99d8546
Add more ignore files to .dockerignore
XanderVertegaal Mar 27, 2026
e236164
Add localhost:5000 to CSRF trusted origins
XanderVertegaal Mar 27, 2026
5203182
Expand Angular Dockerfile.prod based on Docker's Angular containeriza…
XanderVertegaal Mar 27, 2026
a2cabe4
Remove unnecessary proxy.conf.json
XanderVertegaal Mar 27, 2026
fc5ccfe
Revert to port 4200 for frontend prod container
XanderVertegaal Mar 27, 2026
35bd17c
Use correct line-endings in build-pre.js
XanderVertegaal Mar 27, 2026
9831d5a
Match server version of Python (3.11) in backend Dockerfile
XanderVertegaal Mar 27, 2026
5622b83
Remove DB creation script from postgres service.
XanderVertegaal Mar 27, 2026
3c8f01c
Do not mount source code in backend container; use env vars for datab…
XanderVertegaal Mar 27, 2026
8013bad
Use pg_isready as a healthcheck for Postgres
XanderVertegaal Apr 2, 2026
02726cd
Move frontend container command back to Dockerfile.dev
XanderVertegaal Apr 2, 2026
189acc7
Suspend spa_url from urls.py
XanderVertegaal Apr 2, 2026
1fbd233
Use correct user and database in healthcheck
XanderVertegaal Apr 2, 2026
3a37f4d
Update langpro-container
XanderVertegaal Apr 2, 2026
39e5516
Update langpro-container
XanderVertegaal Apr 2, 2026
edf371f
Update LangPro container
XanderVertegaal Apr 2, 2026
6e65a73
Reenable concomitant parse tables
XanderVertegaal Apr 2, 2026
145b074
Back to feature/langpro-submodule in langpro-container
XanderVertegaal Apr 2, 2026
044eee3
Add CSRF token route
XanderVertegaal Apr 15, 2026
b1f03d6
Use LANGPRO_CONTAINER env var in common_settings.py
XanderVertegaal Apr 15, 2026
ec0e9e0
Increase log level for backend-prod
XanderVertegaal Apr 16, 2026
6f51348
Add z to volumes
XanderVertegaal Apr 16, 2026
11c3925
Fix log file path
XanderVertegaal Apr 17, 2026
8afaf24
Hopefully fix postgres healthcheck
XanderVertegaal Apr 17, 2026
0538d9c
Fix log directory
XanderVertegaal Apr 17, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
POSTGRES_USER=
POSTGRES_PASSWORD=
POSTGRES_DB=
DJANGO_SECRET_KEY=
APP_VERSION=
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,6 @@ frontend/src/environments/version.ts
/backend/fracas.xml
/backend/snli_1.0_dev.txt
/backend/snli_1.0_test.txt

# Docker container logs
logs/
22 changes: 19 additions & 3 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -55,20 +55,36 @@ If you are reading this document, you'll likely be working with the integrated p

### Quickstart with Docker

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.

Begin by creating a `.env` file in the project root with the following contents:

```env
POSTGRES_USER=my-user-name
POSTGRES_PASSWORD=my-password
POSTGRES_DB=langpro
DJANGO_SECRET_KEY=my-secret-key
APP_VERSION=1.0.0
```

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.

Finally, run the command below to start the application in development mode.

```console
docker compose up -d
docker compose --profile dev up -d
```

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

```console
docker compose exec backend pytest
docker compose exec backend-dev pytest
```

To run the frontend unittests:

```console
docker compose exec frontend yarn ng test --no-browsers
docker compose exec frontend-dev yarn ng test --no-browsers
```

then open http://localhost:9876 in a browser of choice.
Expand Down
25 changes: 19 additions & 6 deletions backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,11 +1,24 @@
FROM python:3.11
FROM python:3.11-slim-trixie

# Set environment variables.
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

# Install dependencies.
RUN apt update
RUN pip install gunicorn

WORKDIR /usr/src/app/backend
COPY requirements.txt .
RUN pip install -U pip pip-tools && pip-sync
RUN pip install -r requirements.txt --no-cache-dir
Comment thread
XanderVertegaal marked this conversation as resolved.

# Set working directory.
WORKDIR /usr/src/app

# Copy project files.
COPY . .

CMD python manage.py check && \

@XanderVertegaal XanderVertegaal Mar 27, 2026

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I moved this to the compose file so this Dockerfile can be used for both prod (with Gunicorn) and dev (Django dev server). The dev container does not need Gunicorn, of course, but that is a small price to pay for having one container that serves both profiles.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not a fan of using the same image for development and production. In development, I want all images to derive from buildpack-deps in order to save disk space. In production, it is preferable to go for leaner images. If I have to start using those leaner images in development, I will end up with lots of disjoint images on disk that are small in themselves but that effectively make me pay multiple times for software that I tend to use in nearly every project.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Point taken. I definitely support the idea of splitting them up.

python manage.py migrate && \
python manage.py runserver --settings glue --pythonpath .. 0.0.0.0:8000
# Create a directory for Gunicorn logs (production).
RUN mkdir -p /usr/src/app/logs

# Expose port.
EXPOSE 8000
217 changes: 110 additions & 107 deletions backend/langpro_annotator/common_settings.py
Original file line number Diff line number Diff line change
@@ -1,107 +1,110 @@
INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"livereload",
"django.contrib.staticfiles",
"rest_framework",
"django.contrib.sites",
"rest_framework.authtoken",
"dj_rest_auth",
"dj_rest_auth.registration",
"allauth",
"allauth.account",
# Required for deleting accounts, but not actually used,
# cf. https://github.com/iMerica/dj-rest-auth/pull/110.
"allauth.socialaccount",
"user",
"revproxy",
"problem",
"annotation",
]

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"allauth.account.middleware.AccountMiddleware",
]

# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGES = [
("en", "English"),
("nl", "Nederlands"),
]
LANGUAGE_CODE = "en"

TIME_ZONE = "Europe/Amsterdam"

USE_I18N = True

USE_TZ = True # Authentication
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
]
}

AUTH_USER_MODEL = "user.User"
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"]

SITE_ID = 1
SITE_NAME = "langpro_annotator"

# Remove this setting in production!
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

HOST = "localhost:8000"

REST_AUTH = {
"USER_DETAILS_SERIALIZER": "user.serializers.CustomUserDetailsSerializer",
}

LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{levelname} {asctime} {module} {message}",
"style": "{",
},
"simple": {
"format": "{levelname} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "simple",
},
},
"loggers": {
"django": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
"LangProAnnotator": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
},
}

LANGPRO_URL = "http://localhost:8080"
import os


INSTALLED_APPS = [
"django.contrib.admin",
"django.contrib.auth",
"django.contrib.contenttypes",
"django.contrib.sessions",
"django.contrib.messages",
"livereload",
"django.contrib.staticfiles",
"rest_framework",
"django.contrib.sites",
"rest_framework.authtoken",
"dj_rest_auth",
"dj_rest_auth.registration",
"allauth",
"allauth.account",
# Required for deleting accounts, but not actually used,
# cf. https://github.com/iMerica/dj-rest-auth/pull/110.
"allauth.socialaccount",
"user",
"revproxy",
"problem",
"annotation",
]

MIDDLEWARE = [
"django.middleware.security.SecurityMiddleware",
"django.contrib.sessions.middleware.SessionMiddleware",
"django.middleware.locale.LocaleMiddleware",
"django.middleware.common.CommonMiddleware",
"django.middleware.csrf.CsrfViewMiddleware",
"django.contrib.auth.middleware.AuthenticationMiddleware",
"django.contrib.messages.middleware.MessageMiddleware",
"django.middleware.clickjacking.XFrameOptionsMiddleware",
"allauth.account.middleware.AccountMiddleware",
]

# Internationalization
# https://docs.djangoproject.com/en/3.0/topics/i18n/
LANGUAGES = [
("en", "English"),
("nl", "Nederlands"),
]
LANGUAGE_CODE = "en"

TIME_ZONE = "Europe/Amsterdam"

USE_I18N = True

USE_TZ = True # Authentication
REST_FRAMEWORK = {
"DEFAULT_AUTHENTICATION_CLASSES": [
"rest_framework.authentication.TokenAuthentication",
"rest_framework.authentication.SessionAuthentication",
]
}

AUTH_USER_MODEL = "user.User"
ACCOUNT_EMAIL_VERIFICATION = "mandatory"
ACCOUNT_SIGNUP_FIELDS = ["email*", "username*", "password1*", "password2*"]

SITE_ID = 1
SITE_NAME = "langpro_annotator"

# Remove this setting in production!
EMAIL_BACKEND = "django.core.mail.backends.console.EmailBackend"

HOST = "localhost:8000"

REST_AUTH = {
"USER_DETAILS_SERIALIZER": "user.serializers.CustomUserDetailsSerializer",
}

LOGGING = {
"version": 1,
"disable_existing_loggers": False,
"formatters": {
"verbose": {
"format": "{levelname} {asctime} {module} {message}",
"style": "{",
},
"simple": {
"format": "{levelname} {message}",
"style": "{",
},
},
"handlers": {
"console": {
"level": "INFO",
"class": "logging.StreamHandler",
"formatter": "simple",
},
},
"loggers": {
"django": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
"LangProAnnotator": {
"handlers": ["console"],
"level": "INFO",
"propagate": False,
},
},
}

LANGPRO_URL = os.environ.get('LANGPRO_CONTAINER') or 'http://localhost:8080'
15 changes: 12 additions & 3 deletions backend/langpro_annotator/settings.py

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I understand your wish to have one settings.py for development and production, but production settings usually differ more from the development settings than you've accounted for here. You can also see this on the langpro-annotator-prod-containers branch that you created in the deployment repo.

You can a best of both worlds: inject a separate settings.py in deployment. In the injected settings, import * from the settings in the source code. This way, you only have to add/override keys that differ from the development defaults.

Original file line number Diff line number Diff line change
Expand Up @@ -19,13 +19,22 @@
# See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/

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

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

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

ALLOWED_HOSTS = ["la-backend"]

if DEBUG:
ALLOWED_HOSTS.append("localhost")
CSRF_TRUSTED_ORIGINS.extend([
"http://localhost:5000",
"http://127.0.0.1:5000",
])

# Application definition

Expand Down
12 changes: 7 additions & 5 deletions backend/langpro_annotator/urls.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
from rest_framework import routers

from annotation.views import LabelAnnotationView
from langpro_annotator.views import csrf_token
from problem.views.problem import ProblemView

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


if settings.PROXY_FRONTEND:
spa_url = re_path(r"^(?P<path>.*)$", proxy_frontend)
else:
spa_url = re_path(r"", index)
# if settings.PROXY_FRONTEND:
# spa_url = re_path(r"^(?P<path>.*)$", proxy_frontend)
# else:
# spa_url = re_path(r"", index)

urlpatterns = [
path("admin", RedirectView.as_view(url="/admin/", permanent=True)),
Expand All @@ -53,6 +54,7 @@
),
),
path("api/i18n/", i18n),
path("api/csrf", csrf_token),
path("users/", include("user.urls")),
spa_url, # catch-all; unknown paths to be handled by a SPA
# spa_url, # catch-all; unknown paths to be handled by a SPA
]
6 changes: 6 additions & 0 deletions backend/langpro_annotator/views.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
from django.views.decorators.csrf import ensure_csrf_cookie
from django.http import JsonResponse

@ensure_csrf_cookie
def csrf_token(request):
return JsonResponse({"detail": "CSRF cookie set"})
2 changes: 1 addition & 1 deletion backend/requirements.in
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ Django>=4.0.1,<5
djangorestframework
django-livereload-server
django-revproxy>=0.10.0
psycopg2
psycopg2-binary

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

When using psycopg2, the container tries to build its own instance from scratch. The slim container does not have the tools for this but suggests using the psycop2-binary package, which seems to work well and is much faster. I am not what the difference is between installing the binary or building it in the container. Please review this carefully. If we need the non-binary package, we should probably revert to the non-slim container.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

https://www.psycopg.org/docs/install.html#psycopg-vs-psycopg-binary

If we can make it work with the source package, that is probably better. The slimmer the image, the more tools you have to install yourself in the Dockerfile, generally.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right, I'll get it to work with the source package! 👍

pytest
pytest-django
pytest-xdist
Expand Down
2 changes: 1 addition & 1 deletion backend/requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ packaging==24.2
# via pytest
pluggy==1.5.0
# via pytest
psycopg2==2.9.10
psycopg2-binary==2.9.10
# via -r requirements.in
pytest==8.3.5
# via
Expand Down
Loading
Loading