From cadc6f59d1646f3242e6af59f4d3aeaa2c358e38 Mon Sep 17 00:00:00 2001 From: Yunho Ding Date: Fri, 29 Nov 2024 21:10:22 +0800 Subject: [PATCH 1/3] Add production support and update environment configurations - Enhance README with production mode instructions - Modify entrypoint scripts to handle production environment - Update Dockerfile to create access logs directory - Change Django settings module path in ASGI and WSGI files - Adjust static file paths in Django settings - Introduce docker-compose configuration for production - Add script to run development servers for client and server --- README.md | 7 +++++++ docker-compose.prod.yml | 39 +++++++++++++++++++++++++++++++++++++ docker/client/entrypoint.sh | 8 ++++++++ docker/server/Dockerfile | 2 ++ docker/server/entrypoint.sh | 4 ++-- rundev.sh | 2 ++ server/api/asgi.py | 2 +- server/api/settings.py | 4 ++-- server/api/wsgi.py | 2 +- 9 files changed, 64 insertions(+), 6 deletions(-) create mode 100644 docker-compose.prod.yml create mode 100755 rundev.sh diff --git a/README.md b/README.md index 52d8cdc8..b8709dc0 100644 --- a/README.md +++ b/README.md @@ -36,3 +36,10 @@ If you modify anything in the `docker` folder, you need to add the `--build` fla ### Changing env vars Edit the `.env` file in the respective directory (client or server). + +### Production mode + +Use code below to start **Production** mode after modify the `.env` to `PRODUCTION` +```bash +docker compose -f docker-compose.prod.yml up -d --build +``` diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml new file mode 100644 index 00000000..e54251c3 --- /dev/null +++ b/docker-compose.prod.yml @@ -0,0 +1,39 @@ +services: + db: + image: postgres:16.4 + restart: unless-stopped + volumes: + - ${LOCAL_WORKSPACE_FOLDER:-.}/data/db:/var/lib/postgresql/data + healthcheck: + test: ["CMD-SHELL", "pg_isready -U postgres"] + interval: 3s + timeout: 3s + retries: 5 + env_file: ./server/.env + ports: + - 5432:5432 + server: + container_name: wajo-server + build: + context: . + dockerfile: ./docker/server/Dockerfile + restart: unless-stopped + env_file: ./server/.env + ports: + - 8000:8000 + volumes: + - ${LOCAL_WORKSPACE_FOLDER:-.}/server:/app + client: + container_name: wajo-client + build: + context: . + dockerfile: ./docker/client/Dockerfile + restart: unless-stopped + env_file: ./client/.env + ports: + - 3000:3000 + volumes: + - ${LOCAL_WORKSPACE_FOLDER:-.}/client:/app + - ignore:/app/node_modules +volumes: + ignore: \ No newline at end of file diff --git a/docker/client/entrypoint.sh b/docker/client/entrypoint.sh index 0173c5c0..dad9db23 100644 --- a/docker/client/entrypoint.sh +++ b/docker/client/entrypoint.sh @@ -55,4 +55,12 @@ if [ "${APP_ENV^^}" = "DEVELOPMENT" ]; then echo "======= Starting inbuilt nextjs webserver ===================================================================" npm run dev exit +fi + +if [[ "${APP_ENV^^}" = "PRODUCTION" ]]; then + npm install + echo " " + echo "======= Starting inbuilt nextjs webserver ===================================================================" + npm run build + npm run start fi \ No newline at end of file diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 3e886bde..4805766a 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -17,5 +17,7 @@ RUN poetry install COPY ./server ./ +RUN mkdir -p /var/log/accesslogs && chmod -R 777 /var/log/accesslogs + RUN chmod +x /entrypoint.sh CMD ["/entrypoint.sh"] \ No newline at end of file diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index 5d36abfe..451df571 100644 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -39,9 +39,9 @@ fi # Run Django/Gunicorn # =================== if [ "${APP_ENV^^}" = "PRODUCTION" ]; then - + su - # Run Gunicorn / Django printf "\n" && echo " Running Gunicorn / Django" echo "Running: gunicorn api.wsgi -b 0.0.0.0:8000 --workers=6 --keep-alive 20 --log-file=- --log-level debug --access-logfile=/var/log/accesslogs/gunicorn --capture-output --timeout 50" - gunicorn api.wsgi -b 0.0.0.0:8000 --workers=6 --keep-alive 20 --log-file=- --log-level debug --access-logfile=/var/log/accesslogs/gunicorn --capture-output --timeout 50 + poetry run gunicorn api.wsgi -b 0.0.0.0:8000 --workers=6 --keep-alive 20 --log-file=- --log-level debug --access-logfile=/var/log/accesslogs/gunicorn --capture-output --timeout 50 fi \ No newline at end of file diff --git a/rundev.sh b/rundev.sh new file mode 100755 index 00000000..877d11b2 --- /dev/null +++ b/rundev.sh @@ -0,0 +1,2 @@ +#!/bin/bash +(cd server && ./dev.sh) & (cd client && npm run dev ) diff --git a/server/api/asgi.py b/server/api/asgi.py index 6e90b887..90426ca8 100644 --- a/server/api/asgi.py +++ b/server/api/asgi.py @@ -11,6 +11,6 @@ from django.core.asgi import get_asgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings") application = get_asgi_application() diff --git a/server/api/settings.py b/server/api/settings.py index f1e5f46d..846e6cd9 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -146,11 +146,11 @@ STATIC_URL = "/static/" # STATIC_ROOT is where the static files get copied to when "collectstatic" is run. -STATIC_ROOT = "static_files" +STATIC_ROOT = os.path.join(PROJECT_ROOT, "static_files") # This is where to _find_ static files when 'collectstatic' is run. # These files are then copied to the STATIC_ROOT location. -STATICFILES_DIRS = ("static",) +STATICFILES_DIRS = (os.path.join(PROJECT_ROOT, "static"),) # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field diff --git a/server/api/wsgi.py b/server/api/wsgi.py index af988b52..7fc622a1 100644 --- a/server/api/wsgi.py +++ b/server/api/wsgi.py @@ -11,6 +11,6 @@ from django.core.wsgi import get_wsgi_application -os.environ.setdefault("DJANGO_SETTINGS_MODULE", "server.settings") +os.environ.setdefault("DJANGO_SETTINGS_MODULE", "api.settings") application = get_wsgi_application() From a6c41742d0402befa8a45f96b03c7bd8c5e94362 Mon Sep 17 00:00:00 2001 From: Yunho Ding Date: Sun, 2 Mar 2025 04:13:19 +0000 Subject: [PATCH 2/3] Add CI/CD workflow for Docker images and update Docker configurations --- .github/workflows/docker-build.yml | 64 +++++++++++++++++++++++++++++ .github/workflows/template-sync.yml | 4 +- docker-compose.prod.yml | 55 +++++++++++++++++-------- docker/client/Dockerfile | 21 ++++------ docker/client/entrypoint.sh | 5 +-- docker/server/Dockerfile | 9 ++-- docker/server/entrypoint.sh | 10 +++-- 7 files changed, 125 insertions(+), 43 deletions(-) create mode 100644 .github/workflows/docker-build.yml diff --git a/.github/workflows/docker-build.yml b/.github/workflows/docker-build.yml new file mode 100644 index 00000000..54dc954f --- /dev/null +++ b/.github/workflows/docker-build.yml @@ -0,0 +1,64 @@ +name: Docker Image CI/CD + +on: + push: + branches: + - "main" + workflow_dispatch: + +jobs: + backend: + runs-on: ubuntu-latest + + steps: + - name: Check repository + uses: actions/checkout@v4 + + - name: Get date for tagging + id: date + run: echo "date=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV + + - name: Login to DockerHub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build and push the Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/server/Dockerfile + push: true + tags: | + ${{ vars.DOCKERHUB_USERNAME }}/wajo-prod-server:latest + ${{ vars.DOCKERHUB_USERNAME }}/wajo-prod-server:${{ env.date }} + + frontend: + runs-on: ubuntu-latest + + steps: + - name: Check repository + uses: actions/checkout@v4 + + - name: Get date for tagging + id: date + run: echo "date=$(date +'%Y%m%d%H%M%S')" >> $GITHUB_ENV + + # Login + - name: Login to Docker Hub + uses: docker/login-action@v3 + with: + username: ${{ vars.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + # Client Docker image + - name: Build and push the client Docker image + uses: docker/build-push-action@v5 + with: + context: . + file: ./docker/client/Dockerfile + push: true + tags: | + ${{ vars.DOCKERHUB_USERNAME }}/wajo-prod-client:latest + ${{ vars.DOCKERHUB_USERNAME }}/wajo-prod-client:${{ env.date }} diff --git a/.github/workflows/template-sync.yml b/.github/workflows/template-sync.yml index 202b4f67..03d35898 100644 --- a/.github/workflows/template-sync.yml +++ b/.github/workflows/template-sync.yml @@ -18,11 +18,11 @@ jobs: uses: actions/checkout@v4 with: # submodules: true - token: ${{ secrets.TEMPLATE_SYNC_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} - name: actions-template-sync uses: AndreasAugustin/actions-template-sync@v2 with: - github_token: ${{ secrets.GITHUB_TOKEN }} + source_gh_token: ${{ secrets.GITHUB_TOKEN }} source_repo_path: codersforcauses/django-nextjs-template upstream_branch: main diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml index e54251c3..67a861ab 100644 --- a/docker-compose.prod.yml +++ b/docker-compose.prod.yml @@ -13,27 +13,50 @@ services: ports: - 5432:5432 server: - container_name: wajo-server - build: - context: . - dockerfile: ./docker/server/Dockerfile + image: ${DOCKERHUB_USERNAME}/wajo-prod-server:latest + container_name: wajo_server restart: unless-stopped - env_file: ./server/.env - ports: - - 8000:8000 + env_file: ./.env.server + entrypoint: /entrypoint.sh volumes: - - ${LOCAL_WORKSPACE_FOLDER:-.}/server:/app + - ./opt/gunicorn-logs/:/var/log/gunicorn/ + - ./opt/django-logs/:/var/log/django/ + - ./opt/static_files/:/app/static_files + - ./opt/media/:/app/mediafiles/media + depends_on: + - db client: + image: ${DOCKERHUB_USERNAME}/wajo-prod-client:latest container_name: wajo-client - build: - context: . - dockerfile: ./docker/client/Dockerfile restart: unless-stopped - env_file: ./client/.env + env_file: ./.env.client ports: - 3000:3000 + depends_on: + - server + + nginx: + image: nginx + container_name: wajo_nginx + restart: unless-stopped + ports: + - 80:3000 + - 443:3000 + - 3000:3000 + - 8000:8000 + volumes: + - ./custom.conf:/etc/nginx/conf.d/default.conf + - ./opt/static_files:/opt/static_files + - ./opt/media:/opt/media + - ./opt/nginx-logs/:/var/log/nginx/ + depends_on: + - client + - server + + watchtower: + image: containrrr/watchtower + container_name: wajo_watchtower + restart: unless-stopped volumes: - - ${LOCAL_WORKSPACE_FOLDER:-.}/client:/app - - ignore:/app/node_modules -volumes: - ignore: \ No newline at end of file + - /var/run/docker.sock:/var/run/docker.sock + command: --interval 30 diff --git a/docker/client/Dockerfile b/docker/client/Dockerfile index e7492aa8..2e3f3efe 100644 --- a/docker/client/Dockerfile +++ b/docker/client/Dockerfile @@ -1,4 +1,4 @@ -FROM node:20-slim as development-stage +FROM node:20-slim as client-prod # SET WORKING DIRECTORY WORKDIR /app @@ -6,20 +6,15 @@ WORKDIR /app # Copy runtime script & make it executable COPY /docker/client/entrypoint.sh /entrypoint.sh -COPY ./client/package.json ./client/package-lock.json ./ - -# Install ALL Dependencies -RUN npm install +# Make the script executable +RUN chmod +x /entrypoint.sh # Copy Application code into a directory called `app` -COPY ./client /app +COPY ./client ./ -# ======================================== -# ---- Executed at Container Runtime ---- -# ======================================== +# Install dependencies without dev dependencies +RUN npm install --production -# CMD commands get executed at container runtime! -RUN chmod +x /entrypoint.sh -CMD ["/entrypoint.sh"] +RUN npm run build -# TODO: Production \ No newline at end of file +EXPOSE 3000 \ No newline at end of file diff --git a/docker/client/entrypoint.sh b/docker/client/entrypoint.sh index dad9db23..7f7ffae7 100644 --- a/docker/client/entrypoint.sh +++ b/docker/client/entrypoint.sh @@ -58,9 +58,8 @@ if [ "${APP_ENV^^}" = "DEVELOPMENT" ]; then fi if [[ "${APP_ENV^^}" = "PRODUCTION" ]]; then - npm install + # npm install --production echo " " - echo "======= Starting inbuilt nextjs webserver ===================================================================" - npm run build + echo "======= Starting nextjs webserver ===================================================================" npm run start fi \ No newline at end of file diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 4805766a..283f34d4 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -11,13 +11,10 @@ WORKDIR /app COPY ./docker/server/entrypoint.sh /entrypoint.sh -COPY ./server/pyproject.toml ./server/poetry.lock ./ - -RUN poetry install - COPY ./server ./ -RUN mkdir -p /var/log/accesslogs && chmod -R 777 /var/log/accesslogs +RUN poetry install --without dev RUN chmod +x /entrypoint.sh -CMD ["/entrypoint.sh"] \ No newline at end of file + +EXPOSE 8000 \ No newline at end of file diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index 451df571..26b9107f 100644 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -39,9 +39,13 @@ fi # Run Django/Gunicorn # =================== if [ "${APP_ENV^^}" = "PRODUCTION" ]; then - su - + # Create file to hold django logs + printf "\n" && echo "Creating file to hold django logs" + echo "Running: mkdir -p /var/log/django && touch /var/log/django/django.log" + mkdir -p /var/log/django && touch /var/log/django/django.log + # Run Gunicorn / Django printf "\n" && echo " Running Gunicorn / Django" - echo "Running: gunicorn api.wsgi -b 0.0.0.0:8000 --workers=6 --keep-alive 20 --log-file=- --log-level debug --access-logfile=/var/log/accesslogs/gunicorn --capture-output --timeout 50" - poetry run gunicorn api.wsgi -b 0.0.0.0:8000 --workers=6 --keep-alive 20 --log-file=- --log-level debug --access-logfile=/var/log/accesslogs/gunicorn --capture-output --timeout 50 + echo "Running: gunicorn api.wsgi -b 0.0.0.0:8000 --workers=3 --keep-alive 20 --log-level info --access-logfile=/var/log/gunicorn/access.log --error-logfile=/var/log/gunicorn/error.log --capture-output --timeout 50" + gunicorn api.wsgi -b 0.0.0.0:8000 --workers=3 --keep-alive 20 --log-level info --access-logfile=/var/log/gunicorn/access.log --error-logfile=/var/log/gunicorn/error.log --capture-output --timeout 50 fi \ No newline at end of file From 31f080fcdcce66277b816e4148b3d9b448bb0a4b Mon Sep 17 00:00:00 2001 From: Yunho Ding Date: Sun, 2 Mar 2025 04:27:24 +0000 Subject: [PATCH 3/3] Add Nginx configuration and update Gunicorn binding port in Docker setup --- docker/nginx/custom.conf | 21 +++++++++++++++++++++ docker/server/Dockerfile | 4 +--- docker/server/entrypoint.sh | 4 ++-- 3 files changed, 24 insertions(+), 5 deletions(-) create mode 100644 docker/nginx/custom.conf diff --git a/docker/nginx/custom.conf b/docker/nginx/custom.conf new file mode 100644 index 00000000..25b8bf3c --- /dev/null +++ b/docker/nginx/custom.conf @@ -0,0 +1,21 @@ +server { + listen 8000; + server_name localhost; + access_log /var/log/nginx/access.log; + error_log /var/log/nginx/error.log; + client_max_body_size 5M; + + location / { + proxy_pass http://server:8081; + proxy_set_header Host $host; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + } + + location /static/ { + alias /opt/static_files/; + } + + location /media/ { + alias /opt/media/; + } +} \ No newline at end of file diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 283f34d4..b70527e2 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -15,6 +15,4 @@ COPY ./server ./ RUN poetry install --without dev -RUN chmod +x /entrypoint.sh - -EXPOSE 8000 \ No newline at end of file +RUN chmod +x /entrypoint.sh \ No newline at end of file diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index 26b9107f..77cbb722 100644 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -46,6 +46,6 @@ if [ "${APP_ENV^^}" = "PRODUCTION" ]; then # Run Gunicorn / Django printf "\n" && echo " Running Gunicorn / Django" - echo "Running: gunicorn api.wsgi -b 0.0.0.0:8000 --workers=3 --keep-alive 20 --log-level info --access-logfile=/var/log/gunicorn/access.log --error-logfile=/var/log/gunicorn/error.log --capture-output --timeout 50" - gunicorn api.wsgi -b 0.0.0.0:8000 --workers=3 --keep-alive 20 --log-level info --access-logfile=/var/log/gunicorn/access.log --error-logfile=/var/log/gunicorn/error.log --capture-output --timeout 50 + echo "Running: gunicorn api.wsgi -b 0.0.0.0:8081 --workers=3 --keep-alive 20 --log-level info --access-logfile=/var/log/gunicorn/access.log --error-logfile=/var/log/gunicorn/error.log --capture-output --timeout 50" + gunicorn api.wsgi -b 0.0.0.0:8081 --workers=3 --keep-alive 20 --log-level info --access-logfile=/var/log/gunicorn/access.log --error-logfile=/var/log/gunicorn/error.log --capture-output --timeout 50 fi \ No newline at end of file