Skip to content

Commit b19f6af

Browse files
committed
Improved Docker setup
* Environment variables specific to Docker can now be provided using a `.env.docker` file * The image is now based on the latest stable Ubuntu release, like the prod server * Static and media files are persisted between runs (through a volume) * Started using a cache mount for uv, to improve performance across builds * Added Docker-specific `make` commands, all prefixed with `d-` * Stopped always running migrations when starting the container; it's more useful having manual control, using e.g. `make d-migrate`
1 parent 09092c3 commit b19f6af

File tree

7 files changed

+160
-18
lines changed

7 files changed

+160
-18
lines changed

.gitignore

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -474,3 +474,5 @@ pip-selfcheck.json
474474
# End of https://www.toptal.com/developers/gitignore/api/django,pycharm+all,venv,visualstudiocode,macos,node
475475

476476
!src/web/static/lib/
477+
478+
.env.docker

CHANGELOG.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,12 @@ Lastly, a new [release](https://github.com/MAKENTNU/web/releases) must be create
3535
- Replaced `local_settings.py` with `.env` (MAKENTNU/web#767)
3636
- Developers must create a `.env` file locally - see the "Setup" section of the README
3737
- Renamed and added some more `make` commands (MAKENTNU/web#768)
38+
- Improved Docker setup (MAKENTNU/web#768)
39+
- Environment variables specific to Docker can now be provided using a `.env.docker` file
40+
- The image is now based on the latest stable Ubuntu release, like the prod server
41+
- Static and media files are persisted between runs (through a volume)
42+
- Added Docker-specific `make` commands, all prefixed with `d-`
43+
- Stopped always running migrations when starting the container; it's more useful having manual control, using e.g. `make d-migrate`
3844

3945

4046
## 2025-05-03 (MAKENTNU/web#757)

Makefile

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,3 +39,58 @@ start: ## Starts the Django webserver.
3939

4040
test: ## Runs the test suite. Pass extra arguments with `args`, e.g. `args="-k 'test_function'"`.
4141
make manage args="test $(args)"
42+
43+
### Docker ###
44+
45+
COMPOSE_FILE := docker/compose.dev.yaml
46+
47+
d-compose: ## Run Docker Compose for development with `make_ntnu` as project name. Helper command to run any compose command.
48+
docker compose -f '$(COMPOSE_FILE)' -p make_ntnu $(args)
49+
50+
d-build: ## Rebuilds the container image. Use when changes are made to the Dockerfile or the dependencies in `pyproject.toml`.
51+
make d-compose args="build"
52+
53+
d-down: ## Stops and removes the container.
54+
make d-compose args="down"
55+
56+
d-bash: ## Enters a bash shell in an already-running `web` container.
57+
make d-compose args="exec web bash"
58+
59+
d-manage: ## Runs the Django management command specified by passing `args`.
60+
make d-compose args="run --rm web uv run manage.py $(args)"
61+
62+
d-shell: ## Enters a Django shell, with most common classes automatically imported.
63+
make d-manage args="shell_plus"
64+
65+
d-migrate: ## Updates the database schema from the migration files.
66+
make d-manage args="migrate $(args)"
67+
68+
d-makemigrations: ## Creates migration files from the models, if there are any changes.
69+
make d-manage args="makemigrations $(args)"
70+
71+
d-makemessages: ## Extracts all translatable strings from the codebase and updates the `.po` files with them.
72+
make d-manage args="makemessages $(args)"
73+
74+
d-makemessages-all: ## Runs `makemessages` for all languages and domains.
75+
make d-makemessages args="-a"
76+
make d-makemessages args="-a -d djangojs"
77+
78+
d-compilemessages: ## Updates the `.mo` files from the `.po` files.
79+
make d-manage args="compilemessages $(args)"
80+
81+
d-collectstatic: ## "Compiles" the static files (CSS and JS files, images, etc.) into the `STATIC_ROOT` folder.
82+
make d-manage args="collectstatic --no-input"
83+
84+
d-update: ## Updates the container, database and static files.
85+
make d-build
86+
make d-migrate
87+
make d-collectstatic
88+
89+
d-createsuperuser: ## Creates a superuser.
90+
make d-manage args="createsuperuser"
91+
92+
d-start: ## Starts the Django webserver.
93+
make d-compose args="up $(args)"
94+
95+
d-test: ## Runs the test suite. Pass extra arguments with `args`, e.g. `args="-k 'test_function'"`.
96+
make d-manage args="test $(args)"

README.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ and set the following settings (File → Settings...):
5757

5858
### 🚀 Starting the webserver
5959

60+
_We recommend developing using locally installed dependencies, as it's a lot faster and easier to debug, but if you'd like to use Docker (e.g. for
61+
testing stuff in a prod-like environment), follow the instructions under [the "Using Docker" section](#-using-docker) instead._
62+
6063
1. Create an SQLite database file with the proper tables:
6164
```shell
6265
uv run manage.py migrate
@@ -75,6 +78,42 @@ and set the following settings (File → Settings...):
7578
uv run manage.py runserver
7679
```
7780
81+
### 🐋 Using Docker
82+
83+
1. [Install Docker desktop](https://www.docker.com/products/docker-desktop/)
84+
1. Create a `.env.docker` file:
85+
86+
This will contain environment variables that override the ones in `.env`, which will
87+
be used by code running inside Docker.
88+
89+
The following file contents is a good basis:
90+
```dotenv
91+
STATIC_AND_MEDIA_FILES__PARENT_DIR='/vol/web/'
92+
```
93+
1. Build the Docker image and create the database:
94+
```shell
95+
make d-update
96+
```
97+
1. Create an admin user for local development:
98+
```shell
99+
make d-createsuperuser
100+
```
101+
1. Run the server:
102+
* If using PyCharm:
103+
1. [Add a Docker-based Python interpreter](https://www.jetbrains.com/help/pycharm/using-docker-compose-as-a-remote-interpreter.html#docker-compose-remote)
104+
with `make_ntnu` as project name (this should match the `-p` argument in [the `Makefile`](Makefile))
105+
1. Create a "Django Server" [run configuration](https://www.jetbrains.com/help/pycharm/run-debug-configuration.html) using the newly created
106+
Python interpreter, with `0.0.0.0` (instead of `localhost`) as host (this should match the IP address specified by the `command` key in
107+
[`compose.dev.yaml`](docker/compose.dev.yaml))
108+
1. Press the green "play" button in the top right corner
109+
* Otherwise, run:
110+
```shell
111+
make d-start
112+
```
113+
114+
If you encounter any hard-to-fix Docker-related problems, an easy (but drastic) fix can sometimes be to delete the container (`make d-down`)
115+
and follow the steps above again.
116+
78117
### 🧳 Developing offline
79118
80119
When running uv commands, pass [the `--offline` flag](https://docs.astral.sh/uv/reference/cli/#uv-run--offline).

docker/Dockerfile

Lines changed: 39 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,39 @@
1-
FROM python:3.11
2-
ENV PYTHONUNBUFFERED 1
3-
RUN apt update --fix-missing && apt install -y python3-dev libldap2-dev libsasl2-dev libssl-dev gettext libgettextpo-dev
4-
RUN python -m pip install --upgrade pip
5-
RUN pipx install uv
6-
RUN mkdir /web
7-
WORKDIR /web
8-
COPY pyproject.toml uv.lock /web/
9-
RUN uv sync --locked
1+
# Latest stable release - should be the same version as the prod server is running
2+
FROM ubuntu:rolling
3+
4+
ENV PYTHONDONTWRITEBYTECODE=1 \
5+
# Immediately write logs to the console / log file
6+
PYTHONUNBUFFERED=1 \
7+
# https://docs.astral.sh/uv/guides/integration/docker/#compiling-bytecode
8+
UV_COMPILE_BYTECODE=1 \
9+
# https://docs.astral.sh/uv/guides/integration/docker/#caching
10+
UV_LINK_MODE=copy \
11+
# https://docs.astral.sh/uv/concepts/projects/config/#project-environment-path
12+
# (Prevents using/creating a `.venv` folder inside `PROJECT_DIR` when starting
13+
# the container)
14+
UV_PROJECT_ENVIRONMENT=/venv/
15+
16+
# From https://docs.astral.sh/uv/guides/integration/docker/#installing-uv
17+
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /bin/
18+
19+
ARG PROJECT_DIR="/web"
20+
WORKDIR ${PROJECT_DIR}
21+
22+
RUN apt update --fix-missing && \
23+
apt install --yes \
24+
# Necessary for `python-ldap`
25+
build-essential python3-dev libldap2-dev libsasl2-dev libssl-dev \
26+
# Necessary for running `makemessages`/`compilemessages`
27+
gettext libgettextpo-dev && \
28+
# Clean up apt cache lists to reduce image size by removing temporary installation files
29+
rm -rf /var/lib/apt/lists/*
30+
31+
COPY pyproject.toml uv.lock ${PROJECT_DIR}
32+
# https://docs.astral.sh/uv/guides/integration/docker/#caching
33+
RUN --mount=type=cache,target=/root/.cache/uv \
34+
uv sync --locked --group dev
35+
36+
# Create the directories used by `STATIC_ROOT` and `MEDIA_ROOT`
37+
ARG STATIC_DATA_DIR="/vol/web"
38+
RUN mkdir -p ${STATIC_DATA_DIR}/static ${STATIC_DATA_DIR}/media && \
39+
chmod -R 755 ${STATIC_DATA_DIR}

docker/compose.dev.yaml

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,24 @@
1-
version: '3'
2-
31
services:
42
web:
5-
build: ..
6-
command: >
7-
bash -c "uv run manage.py migrate
8-
&& uv run manage.py runserver 0.0.0.0:8000"
9-
volumes:
10-
- ../:/web
3+
platform: linux/amd64
4+
image: make_web
5+
build:
6+
context: ../
7+
dockerfile: ./docker/Dockerfile
8+
command: uv run manage.py runserver 0.0.0.0:8000
119
ports:
1210
- "8000:8000"
1311
env_file:
12+
# The bottom-most envvars will override the ones above
1413
- ../.env
14+
- ../.env.docker
15+
volumes:
16+
# Mounts the parent folder (i.e. the repo folder) as a volume with the same path
17+
# as `PROJECT_DIR` in the Dockerfile, to enable hot reloading of changed local
18+
# files
19+
- ../:/web
20+
# Makes the files inside `STATIC_DATA_DIR` (see the Dockerfile) persistent
21+
- static-data:/vol/web
22+
23+
volumes:
24+
static-data:

src/web/management/commands/runserver.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ def inner_run(self, *args, **options):
1717
if not settings.DEBUG:
1818
super().inner_run(*args, **options)
1919

20-
addr_regex = r"(?:127\.0\.0\.1|localhost)"
20+
addr_regex = r"(?:localhost|127\.0\.0\.1|0\.0\.0\.0)"
2121
dev_server_addr_regex = re.compile(
2222
rf"(Starting .*development server at https?://)({addr_regex}):(\d+)",
2323
re.IGNORECASE

0 commit comments

Comments
 (0)