From 37478e3cb6c813bdc163f9a510bac844a0e52be8 Mon Sep 17 00:00:00 2001 From: Alex-Hawking Date: Sat, 23 Nov 2024 13:01:09 +0800 Subject: [PATCH] update dockerfiles and entrypoints for prod, adjust docker-compose, adjust settings.py --- client/package-lock.json | 23 +++--- client/package.json | 7 +- client/src/components/main/header/Navbar.tsx | 2 +- docker-compose.yml | 32 +++++---- docker/client/Dockerfile | 5 -- docker/client/entrypoint.sh | 75 ++++++++------------ docker/server/Dockerfile | 40 ++++++++--- docker/server/entrypoint.sh | 12 +++- server/.env.production | 3 + server/api/settings.py | 25 +++---- 10 files changed, 123 insertions(+), 101 deletions(-) diff --git a/client/package-lock.json b/client/package-lock.json index 36195c7..432624e 100644 --- a/client/package-lock.json +++ b/client/package-lock.json @@ -48,7 +48,7 @@ "@csstools/postcss-oklab-function": "^3.0.16", "@tanstack/eslint-plugin-query": "^5.43.1", "@types/js-cookie": "^3.0.6", - "@types/node": "^20.14.10", + "@types/node": "20.17.6", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.13.1", @@ -61,8 +61,9 @@ "postcss": "^8", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", + "sonner": "^1.6.1", "tailwindcss": "^3.4.1", - "typescript": "^5.5.2" + "typescript": "5.7.2" } }, "node_modules/@alloc/quick-lru": { @@ -2180,12 +2181,13 @@ "license": "MIT" }, "node_modules/@types/node": { - "version": "20.14.10", - "resolved": "https://registry.npmjs.org/@types/node/-/node-20.14.10.tgz", - "integrity": "sha512-MdiXf+nDuMvY0gJKxyfZ7/6UFsETO7mGKF54MVD/ekJS6HdFtpZFBgrh6Pseu64XTb2MLyFPlbW6hj8HYRQNOQ==", + "version": "20.17.6", + "resolved": "https://registry.npmjs.org/@types/node/-/node-20.17.6.tgz", + "integrity": "sha512-VEI7OdvK2wP7XHnsuXbAJnEpEkF6NjSN45QJlL4VGqZSXsnicpesdTWsg9RISeSdYd3yeRj/y3k5KGjUXYnFwQ==", "dev": true, + "license": "MIT", "dependencies": { - "undici-types": "~5.26.4" + "undici-types": "~6.19.2" } }, "node_modules/@types/prop-types": { @@ -7481,6 +7483,7 @@ "version": "1.7.0", "resolved": "https://registry.npmjs.org/sonner/-/sonner-1.7.0.tgz", "integrity": "sha512-W6dH7m5MujEPyug3lpI2l3TC3Pp1+LTgK0Efg+IHDrBbtEjyCmCHHo6yfNBOsf1tFZ6zf+jceWwB38baC8yO9g==", + "dev": true, "license": "MIT", "peerDependencies": { "react": "^18.0.0 || ^19.0.0 || ^19.0.0-rc", @@ -7982,7 +7985,9 @@ } }, "node_modules/typescript": { - "version": "5.5.3", + "version": "5.7.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.2.tgz", + "integrity": "sha512-i5t66RHxDvVN40HfDd1PsEThGNnlMCMT3jMUuoh9/0TaqWevNontacunWyN02LA9/fIbEWlcHZcgTKb9QoaLfg==", "dev": true, "license": "Apache-2.0", "bin": { @@ -8008,7 +8013,9 @@ } }, "node_modules/undici-types": { - "version": "5.26.5", + "version": "6.19.8", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", + "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true, "license": "MIT" }, diff --git a/client/package.json b/client/package.json index bbdfce3..e2a7627 100644 --- a/client/package.json +++ b/client/package.json @@ -12,7 +12,7 @@ "format": "prettier -w src", "format:check": "prettier -c src", "typecheck": "tsc --noEmit", - "prepare": "cd .. && husky client/.husky" + "prepare": "if [ \"$NODE_ENV\" != \"production\" ]; then cd .. && husky client/.husky; fi" }, "dependencies": { "@hookform/resolvers": "^3.9.0", @@ -55,7 +55,7 @@ "@csstools/postcss-oklab-function": "^3.0.16", "@tanstack/eslint-plugin-query": "^5.43.1", "@types/js-cookie": "^3.0.6", - "@types/node": "^20.14.10", + "@types/node": "20.17.6", "@types/react": "^18.3.3", "@types/react-dom": "^18.3.0", "@typescript-eslint/eslint-plugin": "^7.13.1", @@ -68,7 +68,8 @@ "postcss": "^8", "prettier": "^3.3.2", "prettier-plugin-tailwindcss": "^0.6.5", + "sonner": "^1.6.1", "tailwindcss": "^3.4.1", - "typescript": "^5.5.2" + "typescript": "5.7.2" } } diff --git a/client/src/components/main/header/Navbar.tsx b/client/src/components/main/header/Navbar.tsx index fd219dc..7bb6b8c 100644 --- a/client/src/components/main/header/Navbar.tsx +++ b/client/src/components/main/header/Navbar.tsx @@ -25,7 +25,7 @@ function Links({ isHiddenWhenLg }: { isHiddenWhenLg: boolean }) { Upcoming Events - + About Us diff --git a/docker-compose.yml b/docker-compose.yml index 46ee443..58f9c67 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -3,16 +3,18 @@ services: container_name: coexist-db image: postgres:16 restart: unless-stopped + env_file: ./server/.env volumes: - - ${LOCAL_WORKSPACE_FOLDER:-.}/data/db:/var/lib/postgresql/data + - db-data:/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 + # Remove port mapping in production for security + #ports: + # - "5432:5432" + server: container_name: coexist-server build: @@ -20,10 +22,14 @@ services: dockerfile: ./docker/server/Dockerfile restart: unless-stopped env_file: ./server/.env + depends_on: + - db ports: - - 8000:8000 - volumes: - - ${LOCAL_WORKSPACE_FOLDER:-.}/server:/app + - "8000:8000" + # Remove code volume mounts in production + #volumes: + # - ./server:/app + client: container_name: coexist-client build: @@ -31,11 +37,13 @@ services: dockerfile: ./docker/client/Dockerfile restart: unless-stopped env_file: ./client/.env + depends_on: + - server ports: - - 3000:3000 - volumes: - - ${LOCAL_WORKSPACE_FOLDER:-.}/client:/app - - ignore:/app/node_modules + - "3000:3000" + # Remove code volume mounts in production + #volumes: + # - ./client:/app volumes: - ignore: \ No newline at end of file + db-data: diff --git a/docker/client/Dockerfile b/docker/client/Dockerfile index e7492aa..7de3587 100644 --- a/docker/client/Dockerfile +++ b/docker/client/Dockerfile @@ -8,9 +8,6 @@ COPY /docker/client/entrypoint.sh /entrypoint.sh COPY ./client/package.json ./client/package-lock.json ./ -# Install ALL Dependencies -RUN npm install - # Copy Application code into a directory called `app` COPY ./client /app @@ -21,5 +18,3 @@ COPY ./client /app # CMD commands get executed at container runtime! RUN chmod +x /entrypoint.sh CMD ["/entrypoint.sh"] - -# TODO: Production \ No newline at end of file diff --git a/docker/client/entrypoint.sh b/docker/client/entrypoint.sh index 0173c5c..64ace39 100644 --- a/docker/client/entrypoint.sh +++ b/docker/client/entrypoint.sh @@ -1,58 +1,39 @@ -#!/bin/bash +#!/bin/sh -echo "${APP_NAME^^} - NextJS CONTAINER STARTING..." -echo $APP_NAME +set -e -# Display Docker Image / CI / Release details -echo "Image Build Date/Time: " "$(cat /app/build_timestamp.txt)" "UTC" +echo "$APP_NAME - NextJS CONTAINER STARTING..." +echo "APP_NAME: $APP_NAME" + +# Display Build Date/Time if available +if [ -f /app/build_timestamp.txt ]; then + echo "Image Build Date/Time: $(cat /app/build_timestamp.txt) UTC" +fi echo "-----------------------------------------------------------" echo "APP_ENV: ${APP_ENV}" -# # ==================================================================================== -# # Debug / Sanity check info -# # ==================================================================================== -# echo " " -# echo "======= Current Dir / Files (Debug) =============================================================================" -# pwd -# ls -al - -# echo " " -# echo "======= Env Vars (Debug) ========================================================================================" -# if [ "${APP_ENV^^}" != "PRODUCTION" ]; then -# # Only print environment vars in non-prod environments to prevent sensitive variables being sent to logging system -# printenv -# fi - -# echo " " -# echo "======= Linux version (Debug) ===================================================================================" -# cat /etc/os-release - -# echo " " -# echo "======= Node Path & Version (Debug) ===========================================================================" -# node -v - -# Check for required env vars, exit as failure if missing these critical env vars. -if [[ -z "${APP_ENV}" ]]; then +# Check for required env vars +if [ -z "${APP_ENV}" ]; then echo "█████████████████████████████████████████████████████████████████████████████████████████████████████████████" - echo "█ CRITICAL ERROR: Missing 'APP_ENV' environment variables." + echo "█ CRITICAL ERROR: Missing 'APP_ENV' environment variable." echo "█████████████████████████████████████████████████████████████████████████████████████████████████████████████" - echo "APP_ENV=" $APP_ENV - echo "░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░░" - exit + echo "APP_ENV=${APP_ENV}" + exit 1 fi -# CI TEST DOWN THE TRACK - -# ==================================================================================== -# Run inbuilt nextjs server if ENV is LOCAL -# ==================================================================================== -if [ "${APP_ENV^^}" = "DEVELOPMENT" ]; then - # Install dependencies (idk why it's not installing the latest ones in the docker image) +# Start the application based on APP_ENV +if [ "${APP_ENV}" = "PRODUCTION" ]; then + echo "Starting Next.js application in production mode" + # Build and run for prod npm install - # Run developments - echo " " - echo "======= Starting inbuilt nextjs webserver ===================================================================" - npm run dev - exit -fi \ No newline at end of file + npm run build + npm start +elif [ "${APP_ENV}" = "DEVELOPMENT" ]; then + echo "Starting Next.js application in development mode" + npm install + exec npm run dev +else + echo "Unknown APP_ENV: ${APP_ENV}" + exit 1 +fi diff --git a/docker/server/Dockerfile b/docker/server/Dockerfile index 3e886bd..0f74719 100644 --- a/docker/server/Dockerfile +++ b/docker/server/Dockerfile @@ -1,21 +1,41 @@ +# Use the official Python image FROM python:3.12-slim -RUN apt-get update && apt-get install --yes --no-install-recommends postgresql-client g++ libssl-dev && rm -rf /var/lib/apt/lists/* - -RUN pip install --upgrade pip && pip install poetry - -RUN poetry config virtualenvs.in-project false -RUN poetry config virtualenvs.create false +# Install system dependencies +RUN apt-get update && \ + apt-get install --yes --no-install-recommends \ + build-essential \ + libssl-dev \ + libpq-dev \ + postgresql-client && \ + rm -rf /var/lib/apt/lists/* +# Set work directory WORKDIR /app -COPY ./docker/server/entrypoint.sh /entrypoint.sh +# Install Poetry +RUN pip install --upgrade pip && \ + pip install poetry +# Configure Poetry +RUN poetry config virtualenvs.create false + +# Copy the application files COPY ./server/pyproject.toml ./server/poetry.lock ./ +RUN poetry install --no-dev --no-interaction --no-ansi + +COPY ./server/ ./ -RUN poetry install +# Collect static files +RUN python manage.py collectstatic --noinput --verbosity 2 -COPY ./server ./ +# Create log directory +RUN mkdir -p /var/log/accesslogs && \ + chmod -R 755 /var/log/accesslogs +# Copy entrypoint script +COPY ./docker/server/entrypoint.sh /entrypoint.sh RUN chmod +x /entrypoint.sh -CMD ["/entrypoint.sh"] \ No newline at end of file + +# Start the application +ENTRYPOINT ["/entrypoint.sh"] diff --git a/docker/server/entrypoint.sh b/docker/server/entrypoint.sh index 5d36abf..2dd3032 100644 --- a/docker/server/entrypoint.sh +++ b/docker/server/entrypoint.sh @@ -1,5 +1,9 @@ #!/bin/bash +set -e + +export DJANGO_SETTINGS_MODULE=api.settings + # Wait until Database is available before continuing printf "\n" && echo "Checking Database is up" # using psql @@ -17,9 +21,11 @@ python manage.py migrate --noinput echo "Collecting static files" python manage.py collectstatic --noinput -# Create Django Superuser -echo "Creating Django Superuser" -python manage.py createsuperuser --noinput +# Check if superuser exists +if [ "$APP_ENV" = "DEVELOPMENT" ]; then + echo "Creating Django Superuser" + python manage.py createsuperuser --noinput || true +fi # Run inbuilt Django server if ENV is development if [ "${APP_ENV^^}" = "DEVELOPMENT" ]; then diff --git a/server/.env.production b/server/.env.production index 30d7605..5c6ba5f 100644 --- a/server/.env.production +++ b/server/.env.production @@ -2,7 +2,10 @@ APP_NAME=DjangoAPI APP_ENV=PRODUCTION +# ============= # TO BE CHANGED +# ============= + API_SECRET_KEY="supersecretkey" API_ALLOWED_HOSTS=".localhost 127.0.0.1 [::1]" diff --git a/server/api/settings.py b/server/api/settings.py index 87f084d..df31234 100644 --- a/server/api/settings.py +++ b/server/api/settings.py @@ -11,6 +11,8 @@ """ import os +import datetime + from pathlib import Path from dotenv import load_dotenv @@ -155,18 +157,17 @@ # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/4.2/howto/static-files/ -PROJECT_ROOT = os.path.dirname( - os.path.dirname(os.path.dirname(os.path.abspath(__file__))) -) # <- '/' directory +STATIC_URL = "/static" + +PROJECT_ROOT = os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))) -STATIC_URL = "/static/" +# Will need to configure this for actual deployment +STATIC_ROOT = "/var/www/example.com/static/" -# STATIC_ROOT is where the static files get copied to when "collectstatic" is run. -# STATIC_ROOT = os.path.join(PROJECT_ROOT, "server", "static") +STATICFILES_DIRS = [ + "/static" +] -# This is where to _find_ static files when 'collectstatic' is run. -# These files are then copied to the STATIC_ROOT location. -STATICFILES_DIRS = (os.path.join(PROJECT_ROOT, "server", "static"),) # Default primary key field type # https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field @@ -176,6 +177,6 @@ # DRF Simple JWT Configuration SIMPLE_JWT = { - # "ACCESS_TOKEN_LIFETIME": datetime.timedelta(seconds=10), - # "REFRESH_TOKEN_LIFETIME": datetime.timedelta(seconds=30), -} + "ACCESS_TOKEN_LIFETIME": datetime.timedelta(seconds=10), + "REFRESH_TOKEN_LIFETIME": datetime.timedelta(seconds=30), +} \ No newline at end of file