Skip to content

Commit 8d313fe

Browse files
committed
basic Dockerfile with gunicorn and whitenoise
1 parent 776405d commit 8d313fe

File tree

6 files changed

+167
-1
lines changed

6 files changed

+167
-1
lines changed

Dockerfile

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# based on the uv's example image
2+
3+
FROM ghcr.io/astral-sh/uv:python3.12-bookworm-slim AS builder
4+
5+
ENV UV_COMPILE_BYTECODE=1 UV_LINK_MODE=copy
6+
7+
RUN apt-get update && apt-get install -y libpq5 libpq-dev
8+
9+
WORKDIR /app
10+
11+
RUN --mount=type=cache,target=/root/.cache/uv \
12+
--mount=type=bind,source=uv.lock,target=uv.lock \
13+
--mount=type=bind,source=pyproject.toml,target=pyproject.toml \
14+
uv sync --frozen --no-install-project --no-dev
15+
16+
17+
ADD ./intbot /app/intbot
18+
ADD ./README.md /app
19+
ADD ./Makefile /app
20+
ADD ./pyproject.toml /app
21+
ADD ./uv.lock /app
22+
23+
RUN --mount=type=cache,target=/root/.cache/uv \
24+
uv sync --frozen --no-dev
25+
26+
27+
# Then from builder build the production image
28+
# It needs the very same version of python/base image.
29+
FROM python:3.12-slim-bookworm
30+
31+
RUN apt-get update && apt-get install -y make libpq5
32+
33+
RUN adduser --disabled-password app
34+
35+
COPY --from=builder --chown=app:app /app /app
36+
37+
USER app
38+
39+
WORKDIR /app
40+
41+
ENV PATH="/app/.venv/bin/:$PATH"
42+
43+
RUN make in-container/collectstatic
44+
45+
EXPOSE 4672
46+
47+
# Run the django app by default
48+
CMD ["make", "in-container/gunicorn"]

Makefile

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,13 @@
11
DEV_CMD=cd intbot && DJANGO_ENV="dev" uv run --env-file .env -- ./manage.py
22
TEST_CMD=cd intbot && DJANGO_SETTINGS_MODULE="intbot.settings" DJANGO_ENV="test" uv run pytest --nomigrations
33

4+
DOCKER_RUN_WITH_PORT=docker run -p 4672:4672 --add-host=host.internal:host-gateway -e DJANGO_ENV="local_container" -it intbot:$(V)
5+
6+
7+
# mostly useful for docker and deployment
8+
current_git_hash=$(shell git rev-parse HEAD)
9+
V ?= $(current_git_hash)
10+
411

512
help:
613
# First target is dummy so we you won't run anything by accident
@@ -27,3 +34,23 @@ bot:
2734

2835
client/send_test_webhook:
2936
uv run client/send_test_webhook.py
37+
38+
39+
# Targets to be run inside the container (for build/prod/deployments)
40+
41+
in-container/collectstatic:
42+
DJANGO_ENV="build" python intbot/manage.py collectstatic --noinput
43+
44+
in-container/gunicorn:
45+
# DJANGO_ENV should be passed to docker
46+
cd intbot && gunicorn
47+
48+
in-container/bot:
49+
$(MANAGE) run_bot
50+
51+
52+
docker/build:
53+
docker build . -t intbot:$(current_git_hash)
54+
55+
docker/run/gunicorn:
56+
$(DOCKER_RUN_WITH_PORT) make in-container/gunicorn

intbot/gunicorn.conf.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
bind = "0.0.0.0:4672"
2+
3+
# We want to run it in a small conatiner
4+
workers = 2 + 1
5+
6+
wsgi_app = "intbot.wsgi:application"
7+
8+
# logging
9+
loglevel = "info"
10+
accesslog = "-"
11+
errorlog = "-"
12+
13+
# env
14+
raw_env = ["DJANGO_SETTINGS_MODULE=intbot.settings"]

intbot/intbot/settings.py

Lines changed: 51 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,8 @@
3434

3535
MIDDLEWARE = [
3636
'django.middleware.security.SecurityMiddleware',
37+
# Whitenoise to serve static files
38+
"whitenoise.middleware.WhiteNoiseMiddleware",
3739
'django.contrib.sessions.middleware.SessionMiddleware',
3840
'django.middleware.common.CommonMiddleware',
3941
'django.middleware.csrf.CsrfViewMiddleware',
@@ -97,7 +99,9 @@
9799
# Static files (CSS, JavaScript, Images)
98100
# https://docs.djangoproject.com/en/5.1/howto/static-files/
99101

100-
STATIC_URL = 'static/'
102+
STATIC_URL = "static/"
103+
STATIC_ROOT = BASE_DIR / ".." / "_static_root"
104+
STATICFILES_STORAGE = "whitenoise.storage.CompressedManifestStaticFilesStorage"
101105

102106
# Default primary key field type
103107
# https://docs.djangoproject.com/en/5.1/ref/settings/#default-auto-field
@@ -154,5 +158,51 @@
154158
WEBHOOK_INTERNAL_TOKEN = "test-random-token"
155159

156160

161+
elif DJANGO_ENV == "local_container":
162+
DEBUG = False
163+
ALLOWED_HOSTS = ["127.0.0.1", "localhost"]
164+
165+
SECRET_KEY = "django-insecure-secret"
166+
167+
# Same postgres instance as local dev, but different config
168+
# We're going through the host network to connect to the postgres running
169+
# on the same exposed port as local dev
170+
DATABASES = {
171+
"default": {
172+
"ENGINE": "django.db.backends.postgresql",
173+
"NAME": "intbot_database_dev",
174+
"USER": "intbot_user",
175+
"PASSWORD": "intbot_password",
176+
"HOST": "host.internal",
177+
"PORT": "14672",
178+
}
179+
}
180+
181+
# For 500 errors to appear on stderr
182+
LOGGING = {
183+
"version": 1,
184+
"disable_existing_loggers": False,
185+
"handlers": {
186+
"console": {
187+
"class": "logging.StreamHandler",
188+
"stream": "ext://sys.stderr",
189+
},
190+
},
191+
"loggers": {
192+
"django": {
193+
"handlers": ["console"],
194+
"level": "ERROR",
195+
"propagate": True,
196+
},
197+
},
198+
}
199+
200+
201+
202+
203+
elif DJANGO_ENV == "build":
204+
# Currently used only for collecting staticfiles in docker
205+
DEBUG = False
206+
157207
else:
158208
raise ValueError(f"Unsupported DJANGO_ENV `{DJANGO_ENV}`")

pyproject.toml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,4 +16,6 @@ dependencies = [
1616
"django-extensions>=3.2.3",
1717
"httpx>=0.28.1",
1818
"pytest-asyncio>=0.25.2",
19+
"whitenoise>=6.8.2",
20+
"gunicorn>=23.0.0",
1921
]

uv.lock

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

0 commit comments

Comments
 (0)