From 31cbb6f05a6052847302be78018a41662a9ef32f Mon Sep 17 00:00:00 2001 From: IRBorisov <8611739+IRBorisov@users.noreply.github.com> Date: Tue, 29 Aug 2023 00:34:56 +0300 Subject: [PATCH] Setup dev and prod configurations for docker --- README.md | 46 +++++++++---- docker-compose-dev.yml | 55 +++++++++++++++ docker-compose-prod-local.yml | 71 ++++++++++++++++++++ docker-compose-prod.yml | 4 ++ nginx/Dockerfile | 3 +- nginx/{default.conf => production.conf} | 4 +- nginx/production.local.conf | 41 +++++++++++ postgresql/.env.dev | 5 ++ postgresql/.env.prod.local | 5 ++ rsconcept/PopulateDevData.ps1 | 13 ++++ rsconcept/backend/.env.dev | 27 ++++++++ rsconcept/backend/.env.prod | 2 + rsconcept/backend/.env.prod.local | 27 ++++++++ rsconcept/frontend/.dockerignore | 3 +- rsconcept/frontend/.env.local | 5 ++ rsconcept/frontend/Dockerfile | 3 + rsconcept/frontend/Dockerfile.dev | 17 +++++ rsconcept/frontend/env/.env.development | 5 ++ rsconcept/frontend/env/.env.production | 5 ++ rsconcept/frontend/env/.env.production.local | 6 ++ rsconcept/frontend/src/utils/constants.ts | 13 +--- rsconcept/frontend/vite.config.ts | 40 ++++++----- 22 files changed, 356 insertions(+), 44 deletions(-) create mode 100644 docker-compose-dev.yml create mode 100644 docker-compose-prod-local.yml rename nginx/{default.conf => production.conf} (89%) create mode 100644 nginx/production.local.conf create mode 100644 postgresql/.env.dev create mode 100644 postgresql/.env.prod.local create mode 100644 rsconcept/PopulateDevData.ps1 create mode 100644 rsconcept/backend/.env.dev create mode 100644 rsconcept/backend/.env.prod.local create mode 100644 rsconcept/frontend/.env.local create mode 100644 rsconcept/frontend/Dockerfile.dev create mode 100644 rsconcept/frontend/env/.env.development create mode 100644 rsconcept/frontend/env/.env.production create mode 100644 rsconcept/frontend/env/.env.production.local diff --git a/README.md b/README.md index 062a43b8..fe8f0aca 100644 --- a/README.md +++ b/README.md @@ -2,20 +2,11 @@ React + Django based web portal for editing RSForm schemas. This readme file is used mostly to document project dependencies - -# Developer Setup Notes -- Install Python 3.9, NodeJS, VSCode, Docker Desktop -- copy import wheels from ConceptCore to rsconcept\backend\import -- run rsconcept\backend\LocalEnvSetup.ps1 -- run 'npm install' in rsconcept\frontend -- use VSCode configs in root folder to start developement -- production: create secrets secrets\db_password.txt and django_key.txt -- production: provide TLS certificate nginx\cert\portal-cert.pem and nginx\cert\portal-key.pem - # Contributing notes +!BEFORE PUSHING INTO MAIN! - use Test config in VSCode to run tests before pushing commits / requests -- !BEFORE PUSHING INTO MAIN! in rsconcept\frontend run in terminal 'npm run build' and fix all errors -- when making major changes make sure that Docker production is building correctly. run 'docker compose -f docker-compose-prod.yml up' +- cd rsconcept/frontend & npm run build +- docker compose -f docker-compose-prod.yml up # Frontend stack & Tooling [Vite + React + Typescript]
@@ -62,11 +53,11 @@ This readme file is used mostly to document project dependencies
requirements
-  - tzdata
   - django
   - djangorestframework
   - django-cors-headers
   - django-filter
+  - tzdata
   - gunicorn
   - coreapi
   - psycopg2-binary
@@ -96,3 +87,32 @@ This readme file is used mostly to document project dependencies
 # DevOps
 - Docker compose
 - PowerShell
+- Certbot
+- Docker VSCode extension
+
+# Developer Notes
+## Local build (Windows 10+)
+- this is main developers build
+- Install Python 3.9, NodeJS, VSCode, Docker Desktop
+- copy import wheels from ConceptCore to rsconcept/backend/import
+- run rsconcept/backend/LocalEnvSetup.ps1
+- run 'npm install' in rsconcept/frontend
+- use VSCode configs in root folder to start developement
+
+## Developement build
+- this build does not use HTTPS and nginx for networking
+- backend and frontend debugging is supported
+- hmr (hot updates) for frontend
+- run via 'docker compose -f "docker-compose-dev.yml" up --build -d'
+- populate initial data: rsconcept/PopulateDevData.ps1 dev-portal-backend
+
+## Local production build
+- this build is same as production except not using production secrets and working on localhost
+- provide TLS certificate (can be self-signed) 'nginx/cert/local-cert.pem' and 'nginx/cert/local-key.pem'
+- run via 'docker compose -f "docker-compose-prod-local.yml" up --build -d'
+- populate initial data: rsconcept/PopulateDevData.ps1 local-portal-backend
+
+## Production build
+- create secrets secrets/db_password.txt and django_key.txt
+- provide TLS certificate 'nginx/cert/front-cert.pem' and 'nginx/cert/front-key.pem'
+- run via 'docker compose -f "docker-compose-prod.yml" up --build -d'
diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml
new file mode 100644
index 00000000..e2c34974
--- /dev/null
+++ b/docker-compose-dev.yml
@@ -0,0 +1,55 @@
+name: dev-concept-portal
+
+volumes:
+  postgres_volume:
+    name: "dev-portal-data"
+  django_static_volume:
+    name: "dev-portal-static"
+  django_media_volume:
+    name: "dev-portal-media"
+
+networks:
+  default:
+    name: dev-concept-api-net
+
+services:
+  frontend:
+    container_name: dev-portal-frontend
+    restart: always
+    depends_on:
+      - backend
+    build: 
+      context: ./rsconcept/frontend
+      dockerfile: Dockerfile.dev
+      args:
+        BUILD_TYPE: development
+    ports:
+      - 3002:3002
+    command: npm run dev -- --host
+
+
+  backend:
+    container_name: dev-portal-backend
+    restart: always
+    depends_on:
+      - postgresql-db
+    build:
+      context: ./rsconcept/backend
+    env_file: ./rsconcept/backend/.env.dev
+    ports:
+      - 8002:8002
+    volumes:
+      - django_static_volume:/home/app/web/static
+      - django_media_volume:/home/app/web/media
+    command:
+      gunicorn -w 3 project.wsgi --bind 0.0.0.0:8002
+
+
+  postgresql-db:
+    container_name: dev-portal-db
+    restart: always
+    image: postgres:alpine
+    env_file: ./postgresql/.env.dev
+    volumes:
+      - postgres_volume:/var/lib/postgresql/data
+
diff --git a/docker-compose-prod-local.yml b/docker-compose-prod-local.yml
new file mode 100644
index 00000000..f605ad05
--- /dev/null
+++ b/docker-compose-prod-local.yml
@@ -0,0 +1,71 @@
+name: local-concept-portal
+
+volumes:
+  postgres_volume:
+    name: "local-portal-data"
+  django_static_volume:
+    name: "local-portal-static"
+  django_media_volume:
+    name: "local-portal-media"
+
+networks:
+  default:
+    name: local-concept-api-net
+
+services:
+  frontend:
+    container_name: local-portal-frontend
+    restart: always
+    depends_on:
+      - backend
+    build: 
+      context: ./rsconcept/frontend
+      args:
+        BUILD_TYPE: production.local
+    expose:
+      - 3001
+    command: serve -s /home/node -l 3001
+
+
+  backend:
+    container_name: local-portal-backend
+    restart: always
+    depends_on:
+      - postgresql-db
+    build:
+      context: ./rsconcept/backend
+    env_file: ./rsconcept/backend/.env.prod.local
+    expose:
+      - 8001
+    volumes:
+      - django_static_volume:/home/app/web/static
+      - django_media_volume:/home/app/web/media
+    command:
+      gunicorn -w 3 project.wsgi --bind 0.0.0.0:8001
+
+
+  postgresql-db:
+    container_name: local-portal-db
+    restart: always
+    image: postgres:alpine
+    env_file: ./postgresql/.env.prod.local
+    volumes:
+      - postgres_volume:/var/lib/postgresql/data
+
+
+  nginx:
+    container_name: local-portal-router
+    restart: always
+    build:
+      context: ./nginx
+      args:
+        BUILD_TYPE: production.local
+    ports:
+      - 8001:8001
+      - 3001:3001
+    depends_on:
+      - backend
+    command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
+    volumes:
+      - django_static_volume:/var/www/static
+      - django_media_volume:/var/www/media
diff --git a/docker-compose-prod.yml b/docker-compose-prod.yml
index 465bae31..8edcdd4d 100644
--- a/docker-compose-prod.yml
+++ b/docker-compose-prod.yml
@@ -26,6 +26,8 @@ services:
       - backend
     build: 
       context: ./rsconcept/frontend
+      args:
+        BUILD_TYPE: production
     expose:
       - 3000
     command: serve -s /home/node -l 3000
@@ -72,6 +74,8 @@ services:
     restart: always
     build:
       context: ./nginx
+      args:
+        BUILD_TYPE: production
     ports:
       - 8000:8000
       - 3000:3000
diff --git a/nginx/Dockerfile b/nginx/Dockerfile
index 52307f08..26799954 100644
--- a/nginx/Dockerfile
+++ b/nginx/Dockerfile
@@ -1,5 +1,6 @@
 FROM nginx:stable-alpine3.17-slim
+ARG BUILD_TYPE=production
 
 # Сopу nginx configuration to the proxy-server
-COPY ./default.conf /etc/nginx/conf.d/default.conf
+COPY ./$BUILD_TYPE.conf /etc/nginx/conf.d/default.conf
 COPY ./cert/* /etc/ssl/private/
\ No newline at end of file
diff --git a/nginx/default.conf b/nginx/production.conf
similarity index 89%
rename from nginx/default.conf
rename to nginx/production.conf
index 2e2e0032..7f666e68 100644
--- a/nginx/default.conf
+++ b/nginx/production.conf
@@ -10,7 +10,7 @@ server {
     listen 8000 ssl;
     ssl_certificate /etc/ssl/private/front-cert.pem;
     ssl_certificate_key /etc/ssl/private/front-key.pem;
-    server_name dev.concept.ru www.dev.concept.ru portal.acconcept.ru www.portal.acconcept.ru api.portal.acconcept.ru www.api.portal.acconcept.ru mail.acconcept.ru www.mail.acconcept.ru;
+    server_name dev.concept.ru www.dev.concept.ru portal.acconcept.ru www.portal.acconcept.ru api.portal.acconcept.ru www.api.portal.acconcept.ru;
 
     location / {
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
@@ -30,7 +30,7 @@ server {
     listen 3000 ssl;
     ssl_certificate /etc/ssl/private/front-cert.pem;
     ssl_certificate_key /etc/ssl/private/front-key.pem;
-    server_name dev.concept.ru www.dev.concept.ru portal.acconcept.ru www.portal.acconcept.ru mail.acconcept.ru www.mail.acconcept.ru;
+    server_name dev.concept.ru www.dev.concept.ru portal.acconcept.ru www.portal.acconcept.ru;
     
     location / {
         proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
diff --git a/nginx/production.local.conf b/nginx/production.local.conf
new file mode 100644
index 00000000..68c61b84
--- /dev/null
+++ b/nginx/production.local.conf
@@ -0,0 +1,41 @@
+upstream innerdjango {
+    server backend:8001;
+}
+
+upstream innerreact {
+    server frontend:3001;
+}
+
+server {
+    listen 8001 ssl;
+    ssl_certificate /etc/ssl/private/local-cert.pem;
+    ssl_certificate_key /etc/ssl/private/local-key.pem;
+    server_name localhost;
+
+    location / {
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header Host $host;
+        proxy_pass http://innerdjango;
+        proxy_redirect default;
+    }
+    location /static/ {
+        alias /var/www/static/;
+    }
+    location /media/ {
+        alias /var/www/media/;
+   }
+}
+
+server {
+    listen 3001 ssl;
+    ssl_certificate /etc/ssl/private/local-cert.pem;
+    ssl_certificate_key /etc/ssl/private/local-key.pem;
+    server_name localhost;
+    
+    location / {
+        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+        proxy_set_header Host $host;
+        proxy_pass http://innerreact;
+        proxy_redirect default;
+    }
+}
\ No newline at end of file
diff --git a/postgresql/.env.dev b/postgresql/.env.dev
new file mode 100644
index 00000000..f7e1745f
--- /dev/null
+++ b/postgresql/.env.dev
@@ -0,0 +1,5 @@
+# WARNING! This config does not use 'real' production values for secrets
+# DO NOT use PRODUCTION LOCAL build for deployment!
+POSTGRES_USER=portal-admin
+POSTGRES_DB=portal-db
+POSTGRES_PASSWORD=78ACF6C4F3
\ No newline at end of file
diff --git a/postgresql/.env.prod.local b/postgresql/.env.prod.local
new file mode 100644
index 00000000..f7e1745f
--- /dev/null
+++ b/postgresql/.env.prod.local
@@ -0,0 +1,5 @@
+# WARNING! This config does not use 'real' production values for secrets
+# DO NOT use PRODUCTION LOCAL build for deployment!
+POSTGRES_USER=portal-admin
+POSTGRES_DB=portal-db
+POSTGRES_PASSWORD=78ACF6C4F3
\ No newline at end of file
diff --git a/rsconcept/PopulateDevData.ps1 b/rsconcept/PopulateDevData.ps1
new file mode 100644
index 00000000..d7711d6b
--- /dev/null
+++ b/rsconcept/PopulateDevData.ps1
@@ -0,0 +1,13 @@
+# Initialize database !
+# FOR DEVELOPEMENT BUILDS ONLY!
+$container= Read-Host -Prompt "Enter backend container name: "
+
+docker exec -it $container python manage.py loaddata fixtures/InitialData.json
+
+docker exec `
+    -e DJANGO_SUPERUSER_USERNAME=admin `
+    -e DJANGO_SUPERUSER_PASSWORD=1234 `
+    -e DJANGO_SUPERUSER_EMAIL=admin@admin.com `
+    -it $container python manage.py createsuperuser --noinput
+
+pause
\ No newline at end of file
diff --git a/rsconcept/backend/.env.dev b/rsconcept/backend/.env.dev
new file mode 100644
index 00000000..501f8a22
--- /dev/null
+++ b/rsconcept/backend/.env.dev
@@ -0,0 +1,27 @@
+# Application settings
+# WARNING! This config does not use 'real' production values for secrets
+# DO NOT use PRODUCTION LOCAL build for deployment!
+SECRET_KEY=s-55j!5jlan=x%8-6m1qnst^7s6nwby4dx@vei)5w8t)3_=mv1
+ALLOWED_HOSTS=localhost
+CSRF_TRUSTED_ORIGINS=http://localhost:3002;http://localhost:8002
+CORS_ALLOWED_ORIGINS=http://localhost:3002
+
+
+# File locations
+STATIC_ROOT=/home/app/web/static
+MEDIA_ROOT=/home/app/web/media
+
+
+# Database settings
+DB_ENGINE=django.db.backends.postgresql_psycopg2
+DB_NAME=portal-db
+DB_USER=portal-admin
+DB_HOST=postgresql-db
+DB_PORT=5432
+DB_PASSWORD=78ACF6C4F3
+
+
+# Debug settings
+DEBUG=1
+PYTHONDEVMODE=1
+PYTHONTRACEMALLOC=1
\ No newline at end of file
diff --git a/rsconcept/backend/.env.prod b/rsconcept/backend/.env.prod
index 46551abe..b2e5109f 100644
--- a/rsconcept/backend/.env.prod
+++ b/rsconcept/backend/.env.prod
@@ -1,5 +1,6 @@
 # Application settings
 
+# SECRET_KEY=
 ALLOWED_HOSTS=portal.acconcept.ru;dev.concept.ru
 CSRF_TRUSTED_ORIGINS=https://dev.concept.ru:3000;https://dev.concept.ru:8000;https://portal.acconcept.ru;https://portal.acconcept.ru:8081;https://portal.acconcept.ru:8082
 CORS_ALLOWED_ORIGINS=https://dev.concept.ru:3000;https://portal.acconcept.ru;https://portal.acconcept.ru:8081
@@ -16,6 +17,7 @@ DB_NAME=portal-db
 DB_USER=portal-admin
 DB_HOST=postgresql-db
 DB_PORT=5432
+# DB_PASSWORD=
 
 
 # Debug settings
diff --git a/rsconcept/backend/.env.prod.local b/rsconcept/backend/.env.prod.local
new file mode 100644
index 00000000..da3f2e1e
--- /dev/null
+++ b/rsconcept/backend/.env.prod.local
@@ -0,0 +1,27 @@
+# Application settings
+# WARNING! This config does not use 'real' production values for secrets
+# DO NOT use PRODUCTION LOCAL build for deployment!
+SECRET_KEY=s-55j!5jlan=x%8-6m1qnst^7s6nwby4dx@vei)5w8t)3_=mv1
+ALLOWED_HOSTS=localhost
+CSRF_TRUSTED_ORIGINS=https://localhost:3001;https://localhost:8001
+CORS_ALLOWED_ORIGINS=https://localhost:3001
+
+
+# File locations
+STATIC_ROOT=/home/app/web/static
+MEDIA_ROOT=/home/app/web/media
+
+
+# Database settings
+DB_ENGINE=django.db.backends.postgresql_psycopg2
+DB_NAME=portal-db
+DB_USER=portal-admin
+DB_HOST=postgresql-db
+DB_PORT=5432
+DB_PASSWORD=78ACF6C4F3
+
+
+# Debug settings
+DEBUG=0
+PYTHONDEVMODE=0
+PYTHONTRACEMALLOC=0
\ No newline at end of file
diff --git a/rsconcept/frontend/.dockerignore b/rsconcept/frontend/.dockerignore
index cd7ad448..409a5cb9 100644
--- a/rsconcept/frontend/.dockerignore
+++ b/rsconcept/frontend/.dockerignore
@@ -1,3 +1,4 @@
 # Dev specific
 .gitignore
-node_modules
\ No newline at end of file
+node_modules
+.env.local
\ No newline at end of file
diff --git a/rsconcept/frontend/.env.local b/rsconcept/frontend/.env.local
new file mode 100644
index 00000000..e406b9c7
--- /dev/null
+++ b/rsconcept/frontend/.env.local
@@ -0,0 +1,5 @@
+# Local build config
+
+VITE_PORTAL_BACKEND=http://localhost:8000
+VITE_PORTAL_FRONT_PORT=3000
+VITE_PORTAL_FRONT_HTTPS=false
diff --git a/rsconcept/frontend/Dockerfile b/rsconcept/frontend/Dockerfile
index d7a259f6..b8f293da 100644
--- a/rsconcept/frontend/Dockerfile
+++ b/rsconcept/frontend/Dockerfile
@@ -5,11 +5,14 @@ RUN apt-get update -qq && \
     rm -rf /var/lib/apt/lists/*
 
 # ======= Build =======
+ARG BUILD_TYPE=production
 FROM node-base as builder
 
 WORKDIR /result
 
 COPY ./ ./
+COPY ./env/.env.$BUILD_TYPE ./
+RUN rm -rf ./env
 RUN npm install
 ENV NODE_ENV production
 RUN npm run build
diff --git a/rsconcept/frontend/Dockerfile.dev b/rsconcept/frontend/Dockerfile.dev
new file mode 100644
index 00000000..be4e8c9f
--- /dev/null
+++ b/rsconcept/frontend/Dockerfile.dev
@@ -0,0 +1,17 @@
+# ======== Multi-stage base ==========
+FROM node:bullseye-slim as node-base
+RUN apt-get update -qq && \
+    apt-get upgrade -y && \
+    rm -rf /var/lib/apt/lists/*
+
+# ========= Server =======
+FROM node-base as product-server
+ARG BUILD_TYPE=production
+
+WORKDIR /home
+
+COPY ./ ./
+COPY ./env/.env.$BUILD_TYPE ./
+RUN rm -rf ./env
+
+RUN npm install
diff --git a/rsconcept/frontend/env/.env.development b/rsconcept/frontend/env/.env.development
new file mode 100644
index 00000000..b662d21b
--- /dev/null
+++ b/rsconcept/frontend/env/.env.development
@@ -0,0 +1,5 @@
+# Frontend public settings: Production Local
+
+VITE_PORTAL_BACKEND=http://localhost:8002
+VITE_PORTAL_FRONT_PORT=3002
+VITE_PORTAL_FRONT_HTTPS=false
diff --git a/rsconcept/frontend/env/.env.production b/rsconcept/frontend/env/.env.production
new file mode 100644
index 00000000..82fc5eb2
--- /dev/null
+++ b/rsconcept/frontend/env/.env.production
@@ -0,0 +1,5 @@
+# Frontend public settings: Production
+
+VITE_PORTAL_BACKEND=https://portal.acconcept.ru:8082
+VITE_PORTAL_FRONT_PORT=3000
+VITE_PORTAL_FRONT_HTTPS=true
diff --git a/rsconcept/frontend/env/.env.production.local b/rsconcept/frontend/env/.env.production.local
new file mode 100644
index 00000000..afc1a660
--- /dev/null
+++ b/rsconcept/frontend/env/.env.production.local
@@ -0,0 +1,6 @@
+# Frontend public settings: Production Local
+
+VITE_PORTAL_BACKEND=https://localhost:8001
+VITE_PORTAL_FRONT_PORT=3001
+VITE_PORTAL_FRONT_HTTPS=true
+
diff --git a/rsconcept/frontend/src/utils/constants.ts b/rsconcept/frontend/src/utils/constants.ts
index 226cf559..15e2c5da 100644
--- a/rsconcept/frontend/src/utils/constants.ts
+++ b/rsconcept/frontend/src/utils/constants.ts
@@ -1,16 +1,7 @@
 // Constants
-const prod = {
-  backend: 'https://portal.acconcept.ru:8082',
-  // backend: 'https://dev.concept.ru:8000',
-  // backend: 'https://localhost:8000',
-  // backend: 'https://api.portal.concept.ru',
+export const config = {
+  backend: import.meta.env.VITE_PORTAL_BACKEND as string
 };
-
-const dev = {
-  backend: 'http://localhost:8000',
-};
-
-export const config = process.env.NODE_ENV === 'production' ? prod : dev;
 export const TIMEOUT_UI_REFRESH = 100;
 
 export const youtube = {
diff --git a/rsconcept/frontend/vite.config.ts b/rsconcept/frontend/vite.config.ts
index 8cc2b4a1..a887be0a 100644
--- a/rsconcept/frontend/vite.config.ts
+++ b/rsconcept/frontend/vite.config.ts
@@ -1,5 +1,5 @@
 import react from '@vitejs/plugin-react';
-import { defineConfig } from 'vite';
+import { defineConfig, loadEnv } from 'vite';
 
 import { dependencies } from './package.json'
 
@@ -14,20 +14,28 @@ function renderChunks(deps: Record) {
 }
 
 // https://vitejs.dev/config/
-export default defineConfig({
-  plugins: [react()],
-  server: {
-    port: 3000
-  },
-  build: {
-    chunkSizeWarningLimit: 4000, // KB
-    sourcemap: false,
-    rollupOptions: {
-      output: {
-        manualChunks: {
-          ...renderChunks(dependencies),
+export default (({ mode }: { mode: string }) => {
+  process.env = {...process.env, ...loadEnv(mode, process.cwd())};
+  const enableHttps = process.env.VITE_PORTAL_FRONT_HTTPS === 'true';
+  return defineConfig({
+    plugins: [react()],
+    server: {
+      port: Number(process.env.VITE_PORTAL_FRONT_PORT),
+
+      // NOTE: https is not used for dev builds currently
+      https: enableHttps,
+    },
+    build: {
+      chunkSizeWarningLimit: 4000, // KB
+      sourcemap: false,
+      rollupOptions: {
+        output: {
+          manualChunks: {
+            // Load chunks for dependencies separately
+            ...renderChunks(dependencies),
+          },
         },
       },
-    },
-  }
-})
+    }
+  });
+});