Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
9 changes: 8 additions & 1 deletion .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,14 @@ jobs:
run: |
SKIP_BUILD=1 just docker-serve prod -d
sleep 5
just docker-smoke-test || { docker logs docker_prod_1; exit 1; }
just docker-smoke-test || {
echo "Smoke test failed, attempting to show logs for all containers:"
for c in $(docker ps -a --format '{{.Names}}'); do
echo "Logs for $c:";
docker logs "$c" || true;
done
exit 1
}

- name: Publish docker image
run: |
Expand Down
9 changes: 8 additions & 1 deletion .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,14 @@ jobs:
run: |
just docker-serve prod -d
sleep 5
just docker-smoke-test || { docker logs docker_prod_1; exit 1; }
just docker-smoke-test || {
echo "Smoke test failed, attempting to show logs for all containers:"
for c in $(docker ps -a --format '{{.Names}}'); do
echo "Logs for $c:";
docker logs "$c" || true;
done
exit 1
}

- name: Save docker image
run: |
Expand Down
2 changes: 2 additions & 0 deletions Procfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
release: /usr/bin/env bash /app/deploy/release.sh
web: gunicorn --config /app/deploy/gunicorn/conf.py opencodelists.wsgi
9 changes: 9 additions & 0 deletions deploy/release.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
#!/usr/bin/env bash

# Gets executed during dokku's release phase as specified in Procfile.
# This is where dokku recommends running db migrations.

set -euo pipefail

./manage.py check --deploy
./manage.py migrate
2 changes: 0 additions & 2 deletions docker/Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -171,8 +171,6 @@ COPY . /app
RUN TRUD_API_KEY=dummy-key SECRET_KEY=dummy-key \
python /app/manage.py collectstatic --no-input

ENTRYPOINT ["/app/docker/entrypoints/prod.sh"]

# We set command rather than entrypoint, to make it easier to run different
# things from the cli
CMD ["gunicorn", "--config", "/app/deploy/gunicorn/conf.py", "opencodelists.wsgi"]
Expand Down
8 changes: 0 additions & 8 deletions docker/entrypoints/prod.sh

This file was deleted.

7 changes: 5 additions & 2 deletions docker/justfile
Original file line number Diff line number Diff line change
Expand Up @@ -65,8 +65,8 @@ _create_storage env:
SCRIPT_DIR={{justfile_directory()}}
if ! test -d "$SCRIPT_DIR/storage"; then
mkdir -p "$SCRIPT_DIR/storage"
sudo chown 10003:10003 "$SCRIPT_DIR/storage"
fi
sudo chown 10003:10003 "$SCRIPT_DIR/storage"
fi


Expand All @@ -86,7 +86,10 @@ exec env="dev" *args="bash":


# run a basic functional smoke test against a running opencodelists
smoke-test host="http://localhost:7000":
smoke-test host="http://localhost:7000" env="prod":
#!/bin/bash
set -eu
echo "Running release script (check and migrations)..."
docker compose exec {{ env }} bash /app/deploy/release.sh
echo "Testing service connectivity..."
curl -I {{ host }} -s --compressed --fail --retry 20 --retry-delay 1 --retry-all-errors
62 changes: 62 additions & 0 deletions docs/adr/0005-migrations-during-dokku-in-release-phase.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# ADR: Run Django Migrations in Dokku Release Phase

Date: 2025-07

## Status

Accepted

## Context

Previously, we had a `prod.sh` script that ran the database migrations and then called `exec "$@"` in order to execute whatever command was passed as arguments to the script. The Dockerfile was

```docker
ENTRYPOINT ["/app/docker/entrypoints/prod.sh"]
CMD ["gunicorn", "--config", "/app/deploy/gunicorn/conf.py", "opencodelists.wsgi"]
```

Meaning that when the docker container was run, without additional arguments, this command would execute:

```bash
prod.sh gunicorn --config /app/deploy/gunicorn/conf.py opencodelists.wsgi
```
Causing the migrations to run, and gunicorn to start. You could also pass a command to `docker run` which would then execute (instead of gunicorn) after the migrations.

With this setup, migrations were run every time the container started, including during the web process startup and health checks. This approach had caused issues, including an outage to job-server where migrations were not fully applied before the health checks timed out.

[Dokku recommend](https://dokku.com/docs/advanced-usage/deployment-tasks/) running database migrations in the `release` phase, which is executed before the new web process is started. This ensures migrations are applied atomically and safely, and that the web process only starts after the database schema is up to date.

## Decision

- **Create** a `release.sh` script that runs the migrations and checks:
```bash
#!/bin/bash
set -e

./manage.py check --deploy
./manage.py migrate
```
- **Move** the migration and check logic to the `release` phase in the `Procfile` and add the `web` command:
```
release: /usr/bin/env bash /app/deploy/release.sh
web: gunicorn --config /app/deploy/gunicorn/conf.py opencodelists.wsgi
```
- **Remove** the `ENTRYPOINT` from the production Docker image.
- **Delete** the now-obsolete `prod.sh` entrypoint script.
- **Keep** the `CMD` in the Dockerfile so `docker run` will still start Gunicorn by default, but can be overriden

## Consequences

- Migrations are now run in the correct place (Dokku's release phase), before the web process starts.
- Deploys are safer and more predictable, with no risk of the health checks failing because they start at the same time as the container starts.
- No scripts or documentation referenced or used the old `prod.sh` entrypoint, so no further changes were needed following its deletion.
- The dev docker image overwrites the ENTRYPOINT and so is unaffected by its removal
- The `CMD` in the Dockerfile remains, allowing for overriding of the command when running the container.


## Changes

- `docker/Dockerfile`: Removed ENTRYPOINT, kept CMD for Gunicorn.
- `docker/entrypoints/prod.sh`: Deleted.
- `deploy/release.sh`: Created for running migrations and checks.
- `Procfile`: Added release phase for migrations and checks, and web phase for Gunicorn.
4 changes: 2 additions & 2 deletions justfile
Original file line number Diff line number Diff line change
Expand Up @@ -342,8 +342,8 @@ docker-exec *args="bash": _env


# run tests in docker container
docker-smoke-test host="http://localhost:7000": _env
{{ just_executable() }} docker/smoke-test {{ host }}
docker-smoke-test host="http://localhost:7000" env="prod": _env
{{ just_executable() }} docker/smoke-test {{ host }} {{env}}


# check migrations in the dev docker container
Expand Down