Skip to content

Commit 73eaeaa

Browse files
committed
Enhances Docker setup and static/media file handling
Refactors Dockerfile for a multi-stage build, improving efficiency, and adds health checks for better monitoring. Updates Docker Compose to include separate services for Nginx and Django, enabling static and media file management via shared volumes. Adds static and media file configuration to Django settings, centralizing file paths. Introduces a health check endpoint in the application for improved service monitoring. Extends .gitignore to exclude static and media directories, maintaining a clean repository.
1 parent 1a99e5a commit 73eaeaa

File tree

9 files changed

+441
-64
lines changed

9 files changed

+441
-64
lines changed

.gitignore

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,8 @@ $RECYCLE.BIN/
2929
.LSOverride
3030

3131
# Icon must end with two \r
32-
Icon
32+
Icon
33+
3334

3435
# Thumbnails
3536
._*
@@ -113,6 +114,10 @@ local_settings.py
113114
db.sqlite3
114115
db.sqlite3-journal
115116

117+
# Static and media files
118+
/static/
119+
/media/
120+
116121
# Flask stuff:
117122
instance/
118123
.webassets-cache

Dockerfile

Lines changed: 107 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,117 @@
11
# syntax=docker/dockerfile:1
22

3-
# Comments are provided throughout this file to help you get started.
4-
# If you need more help, visit the Dockerfile reference guide at
5-
# https://docs.docker.com/go/dockerfile-reference/
3+
# Arguments that can be overridden
4+
ARG PYTHON_VERSION=3.12
5+
ARG PYTHON_BASE_IMAGE=slim
6+
ARG APP_USER=appuser
7+
ARG APP_GROUP=appgroup
8+
ARG APP_UID=10001
9+
ARG APP_GID=10001
10+
ARG APP_ROOT=/app
611

7-
# Want to help us make this template better? Share your feedback here: https://forms.gle/ybq9Krt8jtBL3iCk7
12+
# Build stage
13+
FROM python:${PYTHON_VERSION}-${PYTHON_BASE_IMAGE} AS builder
814

9-
ARG PYTHON_VERSION=3.12
10-
FROM python:${PYTHON_VERSION}-slim AS base
11-
12-
# Prevents Python from writing pyc files.
13-
ENV PYTHONDONTWRITEBYTECODE=1
14-
15-
# Keeps Python from buffering stdout and stderr to avoid situations where
16-
# the application crashes without emitting any logs due to buffering.
17-
ENV PYTHONUNBUFFERED=1
18-
19-
WORKDIR /app
20-
21-
# Create a non-privileged user that the app will run under.
22-
# See https://docs.docker.com/go/dockerfile-user-best-practices/
23-
ARG UID=10001
24-
RUN adduser \
25-
--disabled-password \
26-
--gecos "" \
27-
--home "/nonexistent" \
28-
--shell "/sbin/nologin" \
29-
--no-create-home \
30-
--uid "${UID}" \
31-
appuser
32-
33-
# Download dependencies as a separate step to take advantage of Docker's caching.
34-
# Leverage a cache mount to /root/.cache/pip to speed up subsequent builds.
35-
# Leverage a bind mount to requirements.txt to avoid having to copy them into
36-
# into this layer.
15+
# Build arguments
16+
ARG APP_ROOT
17+
ENV PYTHONDONTWRITEBYTECODE=1 \
18+
PYTHONUNBUFFERED=1 \
19+
PIP_NO_CACHE_DIR=1 \
20+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
21+
PIP_DEFAULT_TIMEOUT=100
22+
23+
WORKDIR ${APP_ROOT}
24+
25+
# Install build dependencies
26+
RUN apt-get update && apt-get install -y --no-install-recommends \
27+
build-essential \
28+
curl \
29+
git \
30+
&& rm -rf /var/lib/apt/lists/*
31+
32+
# Install Python dependencies
33+
COPY requirements*.txt ./
3734
RUN --mount=type=cache,target=/root/.cache/pip \
38-
--mount=type=bind,source=requirements.txt,target=requirements.txt \
39-
python -m pip install -r requirements.txt
35+
pip wheel --no-deps --wheel-dir wheels -r requirements.txt && \
36+
pip install -r requirements.txt
4037

41-
# Switch to the non-privileged user to run the application.
42-
USER appuser
38+
# Copy only necessary files for collecting static
39+
COPY manage.py .
40+
COPY .env .env
41+
COPY test_project/ test_project/
42+
COPY apidemo/ apidemo/
43+
COPY static/ static/
4344

44-
# Copy the source code into the container.
45-
COPY . .
45+
# Collect static files
46+
RUN python manage.py collectstatic --noinput
4647

47-
# Expose the port that the application listens on.
48-
EXPOSE 8000
48+
# Final stage
49+
FROM python:${PYTHON_VERSION}-${PYTHON_BASE_IMAGE} AS runtime
50+
51+
# Runtime arguments and environment variables
52+
ARG APP_USER
53+
ARG APP_GROUP
54+
ARG APP_UID
55+
ARG APP_GID
56+
ARG APP_ROOT
57+
58+
ENV PYTHONDONTWRITEBYTECODE=1 \
59+
PYTHONUNBUFFERED=1 \
60+
PIP_NO_CACHE_DIR=1 \
61+
PIP_DISABLE_PIP_VERSION_CHECK=1 \
62+
APP_ROOT=${APP_ROOT}
63+
64+
WORKDIR ${APP_ROOT}
65+
66+
# Install runtime dependencies
67+
RUN apt-get update && apt-get install -y --no-install-recommends \
68+
curl \
69+
&& rm -rf /var/lib/apt/lists/*
4970

71+
# Create app user and group
72+
RUN groupadd --gid ${APP_GID} ${APP_GROUP} && \
73+
useradd --uid ${APP_UID} --gid ${APP_GID} --no-create-home --home-dir /nonexistent \
74+
--shell /sbin/nologin --system ${APP_USER}
75+
76+
# Create necessary directories with proper permissions
77+
RUN mkdir -p ${APP_ROOT}/static ${APP_ROOT}/media /var/run/django && \
78+
chown -R ${APP_USER}:${APP_GROUP} ${APP_ROOT} /var/run/django
79+
80+
# Copy wheels and install dependencies
81+
COPY --from=builder ${APP_ROOT}/wheels /wheels
82+
RUN --mount=type=cache,target=/root/.cache/pip \
83+
pip install --no-cache-dir /wheels/*
84+
85+
# Copy application code and static files
86+
COPY --chown=${APP_USER}:${APP_GROUP} . .
87+
COPY --from=builder --chown=${APP_USER}:${APP_GROUP} ${APP_ROOT}/static ${APP_ROOT}/static
88+
89+
# Copy and set up entrypoint
90+
COPY --chown=${APP_USER}:${APP_GROUP} docker-entrypoint.sh /usr/local/bin/
91+
RUN chmod +x /usr/local/bin/docker-entrypoint.sh
92+
93+
# Set up health check
94+
HEALTHCHECK --interval=30s --timeout=10s --retries=3 --start-period=30s \
95+
CMD curl -f http://localhost:8000/health/ || exit 1
96+
97+
# Set default environment variables for Gunicorn
98+
ENV GUNICORN_WORKERS=4 \
99+
GUNICORN_THREADS=2 \
100+
GUNICORN_WORKER_CLASS=gthread \
101+
GUNICORN_MAX_REQUESTS=1000 \
102+
GUNICORN_MAX_REQUESTS_JITTER=50
103+
104+
# Switch to non-privileged user
105+
USER ${APP_USER}:${APP_GROUP}
106+
107+
EXPOSE 8000
50108

51-
# Run the application.
52-
# Test application
53-
# RUN python test_project/manage.py makemigrations && python test_project/manage.py migrate
54-
CMD ["gunicorn", "--workers=2", "test_project.wsgi", "--bind", "0.0.0.0:8000"]
109+
ENTRYPOINT ["docker-entrypoint.sh"]
110+
CMD gunicorn \
111+
--workers=${GUNICORN_WORKERS} \
112+
--threads=${GUNICORN_THREADS} \
113+
--worker-class=${GUNICORN_WORKER_CLASS} \
114+
--bind=0.0.0.0:8000 \
115+
--max-requests=${GUNICORN_MAX_REQUESTS} \
116+
--max-requests-jitter=${GUNICORN_MAX_REQUESTS_JITTER} \
117+
test_project.wsgi:application

apidemo/health_check.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
from django.http import HttpResponse
2+
from django.urls import path
3+
4+
def health_check(request):
5+
return HttpResponse("ok")
6+
7+
urlpatterns = [
8+
path('health/', health_check, name='health_check'),
9+
]

compose.yaml

Lines changed: 57 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -2,29 +2,70 @@
22
# If you need more help, visit the Docker Compose reference guide at
33
# https://docs.docker.com/go/compose-spec-reference/
44

5-
# Here the instructions define your application as a service called "server".
6-
# This service is built from the Dockerfile in the current directory.
7-
# You can add other services your application may depend on here, such as a
8-
# database or a cache. For examples, see the Awesome Compose repository:
9-
# https://github.com/docker/awesome-compose
5+
version: '3.8' # Specify the version of Docker Compose
6+
107
services:
11-
server:
8+
nginx:
129
build:
13-
context: .
10+
context: ./nginx
1411
ports:
15-
- 8000:8000
12+
- "80:80"
13+
volumes:
14+
- ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf
15+
- static_volume:/usr/share/nginx/html/static
16+
- media_volume:/usr/share/nginx/html/media
17+
depends_on:
18+
web:
19+
condition: service_healthy
20+
networks:
21+
- app_network
22+
# Add health check for nginx itself
23+
healthcheck:
24+
test: ["CMD", "nginx", "-t"]
25+
interval: 30s
26+
timeout: 10s
27+
retries: 3
28+
29+
web:
30+
build:
31+
context: .
32+
expose:
33+
- 8000
34+
volumes:
35+
- .:/app
36+
- static_volume:/app/static
37+
- media_volume:/app/media
1638
env_file:
1739
- .env
1840
healthcheck:
19-
test: CMD ["python", "manage.py", "check"]
20-
interval: 1m30s
21-
timeout: 30s
22-
retries: 5
41+
test: ["CMD-SHELL", "curl -f http://localhost:8000/health/ || exit 1"]
42+
interval: 30s
43+
timeout: 10s
44+
retries: 3
2345
start_period: 30s
24-
# For creation of replicas
25-
# deploy:
26-
# mode: replicated
27-
# replicas: 2
46+
deploy:
47+
mode: replicated
48+
replicas: 2
49+
update_config:
50+
parallelism: 1
51+
delay: 10s
52+
order: start-first
53+
environment:
54+
- GUNICORN_WORKERS=4
55+
- GUNICORN_THREADS=2
56+
- GUNICORN_WORKER_CLASS=gthread
57+
- GUNICORN_MAX_REQUESTS=1000
58+
- GUNICORN_MAX_REQUESTS_JITTER=50
59+
networks:
60+
- app_network
61+
62+
networks:
63+
app_network:
64+
driver: bridge
65+
66+
volumes:
67+
static_volume:
68+
media_volume:
2869

2970
# The commented out section below is an example of how to define a PostgreSQL
3071
# database that your application can use. `depends_on` tells Docker Compose to
@@ -58,4 +99,3 @@ services:
5899
# secrets:
59100
# db-password:
60101
# file: db/password.txt
61-

docker-entrypoint.sh

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
#!/bin/bash
2+
set -eo pipefail
3+
4+
# Create runtime directory if it doesn't exist
5+
mkdir -p /var/run/django
6+
7+
# Create media directory if it doesn't exist
8+
mkdir -p "${APP_ROOT}/media"
9+
10+
# Apply database migrations
11+
echo "Applying database migrations..."
12+
python manage.py migrate --noinput
13+
14+
# Create health check endpoint
15+
cat > apidemo/health_check.py << EOF
16+
from django.http import HttpResponse
17+
from django.urls import path
18+
19+
def health_check(request):
20+
return HttpResponse("ok")
21+
22+
urlpatterns = [
23+
path('health/', health_check, name='health_check'),
24+
]
25+
EOF
26+
27+
# Start the application
28+
echo "Starting application..."
29+
exec "$@"

0 commit comments

Comments
 (0)