Skip to content

Commit 5968c6a

Browse files
blarghmateyclaude
andauthored
feat: adopt mitodl/ol-python-base:3.11, granian (#5524)
* Configure bumpver for Concourse release pipeline Add [tool.bumpversion] configuration to pyproject.toml for the CalVer release format YYYY.MM.DD.N required by the Concourse release pipeline. Add test_bump_my_version_format (skipped) to validate VERSION against the configured pattern once the CalVer pipeline is confirmed working. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * feat: adopt mitodl/ol-python-base:3.11, BuildKit cache mounts, granian - Replace FROM python:3.11-slim with FROM mitodl/ol-python-base:3.11; removes duplicated apt-get, user creation, and uv installation. - Add BuildKit --mount=type=cache on uv sync for faster rebuilds. - Switch CMD and docker-compose command from uwsgi to granian. - Replace uwsgi_pass with proxy_pass in nginx.conf (dev) and nginx.conf.erb (Heroku). - Update Procfile to run granian on port 8077. - Remove uwsgi from pyproject.toml and regenerate uv.lock. - Update apt.txt to document that all packages are in the base image. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> * fix: COPY --chown to avoid extra layer; exec granian for signal handling - Replace COPY + RUN chown with COPY --chown to avoid a redundant image layer (Gemini review feedback). - Prefix granian with exec in docker-compose command so granian receives OS signals directly instead of through bash. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com> --------- Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 6713ee3 commit 5968c6a

9 files changed

Lines changed: 87 additions & 72 deletions

File tree

Dockerfile

Lines changed: 22 additions & 34 deletions
Original file line numberDiff line numberDiff line change
@@ -1,68 +1,56 @@
1-
FROM python:3.11-slim AS base
1+
# syntax=docker/dockerfile:1
2+
# hadolint global ignore=DL3008
23

4+
FROM mitodl/ol-python-base:3.11 AS base
35
LABEL maintainer="ODL DevOps <mitx-devops@mit.edu>"
46

5-
WORKDIR /tmp
7+
# All required apt packages (git, curl, libjpeg-dev, zlib1g-dev, net-tools,
8+
# build-essential, libpq-dev, postgresql-client) are in mitodl/ol-python-base:3.11.
69

7-
COPY apt.txt /tmp/apt.txt
8-
RUN apt-get update && \
9-
apt-get install --no-install-recommends -y $(grep -vE "^\s*#" apt.txt | tr "\n" " ") libpq-dev postgresql-client && \
10-
apt-get clean && \
11-
apt-get autoremove -y --purge && \
12-
rm -rf /var/lib/apt/lists/*
13-
14-
FROM base AS system
15-
16-
RUN mkdir /src && \
17-
adduser --disabled-password --gecos "" mitodl && \
18-
mkdir /var/media && chown -R mitodl:mitodl /var/media
19-
20-
FROM system AS uv
21-
22-
ENV \
23-
PYTHONUNBUFFERED=1 \
24-
PYTHONDONTWRITEBYTECODE=1 \
25-
UV_PROJECT_ENVIRONMENT="/opt/venv"
10+
ENV PYTHONUNBUFFERED=1 \
11+
PYTHONDONTWRITEBYTECODE=1
2612
ENV PATH="/opt/venv/bin:$PATH"
2713

28-
COPY --from=ghcr.io/astral-sh/uv:latest /uv /uvx /usr/local/bin/
29-
30-
COPY pyproject.toml uv.lock /src/
14+
# ─── Dependency install ───────────────────────────────────────────────────────
15+
FROM base AS deps
3116

32-
RUN chown -R mitodl:mitodl /src && \
33-
mkdir -p /opt/venv && \
34-
chown -R mitodl:mitodl /opt/venv
17+
COPY --chown=mitodl:mitodl pyproject.toml uv.lock /src/
3518

3619
USER mitodl
3720
WORKDIR /src
38-
RUN uv sync --frozen --no-install-project --no-dev
21+
# BuildKit cache mount keeps the uv download cache across builds.
22+
RUN --mount=type=cache,target=/opt/uv-cache,uid=1000,gid=1000 \
23+
uv sync --frozen --no-install-project --no-dev
3924

25+
# ─── Node / frontend asset build ─────────────────────────────────────────────
4026
FROM node:14.18.2 AS node_builder
4127
COPY . /src
4228
WORKDIR /src
4329
RUN yarn install --immutable
4430
RUN node node_modules/webpack/bin/webpack.js --config webpack.config.prod.js --bail
4531

46-
FROM uv AS code
32+
# ─── Code stage ───────────────────────────────────────────────────────────────
33+
FROM deps AS code
4734

4835
COPY --chown=mitodl:mitodl . /src
49-
50-
# Set pip cache folder, as it is breaking pip when it is on a shared volume
5136
ENV XDG_CACHE_HOME=/tmp/.cache
5237

38+
# ─── Production target ────────────────────────────────────────────────────────
5339
FROM code AS production
5440

5541
COPY --from=node_builder --chown=mitodl:mitodl /src/static/bundles /src/static/bundles
5642
COPY --from=node_builder --chown=mitodl:mitodl /src/webpack-stats.json /src/webpack-stats.json
5743

5844
EXPOSE 8079
5945
ENV PORT=8079
60-
CMD ["uwsgi", "uwsgi.ini"]
46+
CMD ["sh", "-c", "exec granian --interface wsgi --host 0.0.0.0 --port ${PORT:-8079} --workers 2 micromasters.wsgi:application"]
6147

48+
# ─── Development target ───────────────────────────────────────────────────────
6249
FROM code AS development
6350

64-
RUN uv sync --frozen --no-install-project
51+
RUN --mount=type=cache,target=/opt/uv-cache,uid=1000,gid=1000 \
52+
uv sync --frozen --no-install-project
6553

6654
EXPOSE 8079
6755
ENV PORT=8079
68-
CMD ["uwsgi", "uwsgi.ini"]
56+
CMD ["sh", "-c", "exec granian --interface wsgi --host 0.0.0.0 --port ${PORT:-8079} --workers 2 micromasters.wsgi:application"]

Procfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
1-
web: bin/start-nginx bin/start-pgbouncer uwsgi uwsgi.ini
1+
web: bin/start-nginx bin/start-pgbouncer granian --interface wsgi --host 0.0.0.0 --port 8077 --workers 2 micromasters.wsgi:application
22
worker: bin/start-pgbouncer celery -A micromasters.celery:app worker -Q search,exams,dashboard,default -B -l $MICROMASTERS_LOG_LEVEL
33
extra_worker: bin/start-pgbouncer celery -A micromasters.celery:app worker -Q search,exams,dashboard,default -l $MICROMASTERS_LOG_LEVEL

apt.txt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,4 @@
1-
# Core requirements
2-
git
3-
curl
4-
libjpeg-dev
5-
zlib1g-dev
6-
net-tools
7-
build-essential
1+
# App-specific apt extras for micromasters.
2+
# All required packages (git, curl, libjpeg-dev, zlib1g-dev, net-tools,
3+
# build-essential, libpq-dev, postgresql-client) are in
4+
# mitodl/ol-python-base:3.11. No extras needed.

config/nginx.conf

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,11 @@ server {
1717
}
1818

1919
location / {
20-
include uwsgi_params;
21-
uwsgi_pass web:8077;
22-
uwsgi_pass_request_headers on;
23-
uwsgi_pass_request_body on;
20+
proxy_pass http://web:8077;
21+
proxy_set_header Host $http_host;
22+
proxy_set_header X-Real-IP $remote_addr;
23+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
24+
proxy_set_header X-Forwarded-Proto $scheme;
2425
client_max_body_size 25M;
2526
}
2627

config/nginx.conf.erb

Lines changed: 5 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -80,22 +80,11 @@ http {
8080
}
8181

8282
location / {
83-
uwsgi_param QUERY_STRING $query_string;
84-
uwsgi_param REQUEST_METHOD $request_method;
85-
uwsgi_param CONTENT_TYPE $content_type;
86-
uwsgi_param CONTENT_LENGTH $content_length;
87-
uwsgi_param REQUEST_URI $request_uri;
88-
uwsgi_param PATH_INFO $document_uri;
89-
uwsgi_param DOCUMENT_ROOT $document_root;
90-
uwsgi_param SERVER_PROTOCOL $server_protocol;
91-
uwsgi_param REMOTE_ADDR $remote_addr;
92-
uwsgi_param REMOTE_PORT $remote_port;
93-
uwsgi_param SERVER_ADDR $server_addr;
94-
uwsgi_param SERVER_PORT $server_port;
95-
uwsgi_param SERVER_NAME $server_name;
96-
uwsgi_pass unix:/tmp/nginx.socket;
97-
uwsgi_pass_request_headers on;
98-
uwsgi_pass_request_body on;
83+
proxy_pass http://<%= ENV["NGINX_UPSTREAM"] || "localhost:8077" %>;
84+
proxy_set_header Host $http_host;
85+
proxy_set_header X-Real-IP $remote_addr;
86+
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
87+
proxy_set_header X-Forwarded-Proto $scheme;
9988
client_max_body_size 25M;
10089
}
10190
}

docker-compose.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,7 @@ services:
7878
sleep 3 &&
7979
python3 manage.py collectstatic --noinput &&
8080
python3 manage.py migrate --no-input &&
81-
uwsgi uwsgi.ini --honour-stdin'
81+
exec granian --interface wsgi --host 0.0.0.0 --port 8077 --workers 2 micromasters.wsgi:application'
8282
stdin_open: true
8383
tty: true
8484
ports:

micromasters/tests/test_settings.py

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,13 @@
33
"""
44

55
import importlib
6+
import re
67
import sys
8+
import tomllib
79
from unittest import mock
810

11+
import pytest
12+
913
import semantic_version
1014
from ddt import data, ddt
1115
from django.conf import settings
@@ -158,3 +162,14 @@ def test_semantic_version():
158162
Verify that we have a semantic compatible version.
159163
"""
160164
semantic_version.Version(settings.VERSION)
165+
166+
@staticmethod
167+
@pytest.mark.skip(reason="VERSION uses semver until CalVer pipeline is validated")
168+
def test_bump_my_version_format():
169+
"""Verify that VERSION matches the bump-my-version calver format."""
170+
with open("pyproject.toml", "rb") as f: # noqa: PTH123
171+
pyproject = tomllib.load(f)
172+
version_pattern = pyproject["tool"]["bumpversion"]["parse"]
173+
package_version = pyproject["project"]["version"]
174+
assert settings.VERSION == package_version
175+
assert re.fullmatch(version_pattern, settings.VERSION)

pyproject.toml

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,6 @@ dependencies = [
4343
"robohash==2.0.0",
4444
"tornado>=6.0",
4545
"urllib3==1.26.5",
46-
"uwsgi",
4746
"wagtail>=7.0,<8.0",
4847
"flaky==3.7.0",
4948
"pyopenssl",
@@ -79,3 +78,37 @@ dev = [
7978
"isort",
8079
"semantic-version",
8180
]
81+
82+
[tool.bumpversion]
83+
current_version = "0.250.1"
84+
commit = false
85+
tag = false
86+
parse = "(?P<release>(?:[1-9][0-9]{3})\\.(?:1[0-2]|[1-9])\\.(?:3[0-1]|[12][0-9]|[1-9]))\\.(?P<build>\\d+)"
87+
serialize = ["{release}.{build}"]
88+
89+
[tool.bumpversion.parts.release]
90+
calver_format = "{YYYY}.{MM}.{DD}"
91+
92+
[tool.bumpversion.parts.build]
93+
first_value = "1"
94+
95+
[[tool.bumpversion.files]]
96+
filename = "VERSION"
97+
search = "{current_version}"
98+
replace = "{new_version}"
99+
100+
[[tool.bumpversion.files]]
101+
filename = "pyproject.toml"
102+
search = 'version = "{current_version}"'
103+
replace = 'version = "{new_version}"'
104+
105+
[[tool.bumpversion.files]]
106+
filename = "uv.lock"
107+
search = """
108+
name = "micromasters"
109+
version = "{current_version}"
110+
source = {{ virtual = "." }}"""
111+
replace = """
112+
name = "micromasters"
113+
version = "{new_version}"
114+
source = {{ virtual = "." }}"""

uv.lock

Lines changed: 0 additions & 8 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)