I am trying to create a Docker setup with multiple services including Django, Next.js, and Nginx, where Nginx serves as a reverse proxy for both Django (backend) and Next.js (frontend) applications. I want it to work in such a way that these 3 images are combined into one image for deployment. I have tried to used the multi-staged docker build, but i can't get the configuration right.
PROJECT FILES:
./schoolduo-backend/Dockerfile.prod:
###########
# BUILDER #
###########
# pull official base image
FROM python:3.11.4-slim-buster as builder
# set work directory
WORKDIR /usr/src/schoolduo-backend
# set environment variables
ENV PYTHONDONTWRITEBYTECODE 1
ENV PYTHONUNBUFFERED 1
# install system dependencies
RUN apt-get update && \
apt-get install -y --no-install-recommends gcc
# lint
RUN pip install --upgrade pip
RUN pip install flake8
COPY . /usr/src/schoolduo-backend/
RUN flake8 --exclude env --ignore=E501,F401 .
# install python dependencies
COPY ./requirements.txt .
RUN pip wheel --no-cache-dir --no-deps --wheel-dir /usr/src/schoolduo-backend/wheels -r requirements.txt
#########
# FINAL #
#########
# pull official base image
FROM python:3.11.4-slim-buster
# create directory for the app user
RUN mkdir -p /home/app
# create the app user
RUN addgroup --system app && adduser --system --group app
# create the appropriate directories
ENV HOME=/home/app
ENV APP_HOME=/home/app/web
RUN mkdir $APP_HOME
RUN mkdir $APP_HOME/staticfiles
RUN mkdir $APP_HOME/mediafiles
WORKDIR $APP_HOME
# install dependencies
RUN apt-get update && apt-get install -y --no-install-recommends netcat
COPY --from=builder /usr/src/schoolduo-backend/wheels /wheels
COPY --from=builder /usr/src/schoolduo-backend/requirements.txt .
RUN pip install --upgrade pip
RUN pip install --no-cache /wheels/*
# copy entrypoint.prod.sh
COPY ./entrypoint.prod.sh .
RUN sed -i 's/\r$//g' $APP_HOME/entrypoint.prod.sh
RUN chmod +x $APP_HOME/entrypoint.prod.sh
# copy project
COPY . $APP_HOME
# chown all the files to the app user
RUN chown -R app:app $APP_HOME
# change to the app user
USER app
# run entrypoint.prod.sh
ENTRYPOINT ["/home/app/web/entrypoint.prod.sh"]
./schoolduo-frontend/Dockerfile.prod:
FROM node:18-alpine as base
RUN apk add --no-cache g++ make py3-pip libc6-compat
WORKDIR /schoolduo-frontend
COPY package*.json ./
RUN npm install
COPY . .
EXPOSE 3000
RUN npm run build
CMD npm run dev
./nginx/Dockerfile:
FROM nginx:1.25
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d
./nginx/nginx.conf:
proxy_cache_path /var/cache/nginx levels=1:2 keys_zone=STATIC:10m inactive=7d use_temp_path=off;
upstream nextjs {
server frontend:3000;
}
upstream api {
server backend:8000;
}
server {
listen 80 default_server;
server_name _;
server_tokens off;
gzip on;
gzip_proxied any;
gzip_comp_level 4;
gzip_types text/css application/javascript image/svg+xml;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
location /_next/static {
proxy_cache STATIC;
proxy_pass http://nextjs;
# For testing cache - remove before deploying to production
add_header X-Cache-Status $upstream_cache_status;
}
location /static {
proxy_cache STATIC;
proxy_ignore_headers Cache-Control;
proxy_cache_valid 60m;
proxy_pass http://nextjs;
# For testing cache - remove before deploying to production
# add_header X-Cache-Status $upstream_cache_status;
}
location / {
proxy_pass http://nextjs;
}
location ^~ /api/ {
proxy_pass http://api;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header Host $host;
proxy_redirect off;
client_max_body_size 100M;
}
location /static/ {
alias /home/app/web/staticfiles/;
}
location /media/ {
alias /home/app/web/mediafiles/;
}
}
docker-compose.yml:
version: '3.8'
services:
backend:
image: schoolduo-backend
build:
context: ./schoolduo-backend
dockerfile: Dockerfile.prod
command: gunicorn schoolduo.wsgi:application --bind 0.0.0.0:8000
volumes:
- ./schoolduo-backend/:/usr/src/schoolduo-backend/
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
ports:
- 8000:8000
expose:
- 8000
env_file:
- ./.env.prod
frontend:
image: schoolduo-frontend
build:
context: ./schoolduo-frontend
dockerfile: Dockerfile.prod
volumes:
- ./schoolduo-frontend/:/schoolduo-frontend/
ports:
- 3000:3000
env_file:
- ./.env.prod
depends_on:
- backend
nginx:
build:
context: ./nginx
volumes:
- static_volume:/home/app/web/staticfiles
- media_volume:/home/app/web/mediafiles
ports:
- 80:80
depends_on:
- backend
- frontend
volumes:
schoolduo-backend:
schoolduo-frontend:
static_volume:
media_volume:
When I build the container using the docker-compose.yml, i get 3 different images schoolduo-backend, schoolduo-frontend and schoolduo-web-ngnix
with each running at ports 8000, 3000, 80 respectively, but the port 80 is what i need, which i assume it depends on the backend & frontend services, my challenge is how do i combine these 3 images into a single docker image for deployment purposes, because ngnix seems to not work when the image is ran independently, as it shows 502 error
I have tried to a multi-staged build, by combining each dockerfile content into one dockerfile but i don't think i fully understand how to configure it properly