diff --git a/README.md b/README.md
index 31ba8f49..6315c84f 100644
--- a/README.md
+++ b/README.md
@@ -42,6 +42,7 @@ The app depends these libraries and frameworks.
| COMMIT_ID | Default branch's commit sha related to the `RELEASE_VERSION` |
| OPENGRAPH_IMAGE_URL | URL of a 1200x630 image to be used as an OpenGraph thumbnail |
| BASE_URL | The base URL for the frontend of the documentation website. |
+ | WATCHPACK_POLLING | Enables hot reload on NextJS apps running inside Docker containers on a Windows host. Set it to true if running Docker Desktop with WSL2 on a Windows OS. |
## Usage
@@ -50,8 +51,38 @@ The app depends these libraries and frameworks.
cd docs
npm run dev
```
-2. Add or edit MDX files in the `/pages` directory, or add React components in the `/components` directory.
+2. (Optional) Run the app for local development using Docker. Navigate to the project's root directory then run:
+ ```
+ # 2.1. Build the client and server containers for localhost development.
+ docker compose -f docker-compose.dev.yml build
+
+ # 2.2. Create and start the development client and server containers
+ docker compose -f docker-compose.dev.yml up
+
+ # 2.3. Stop and remove the development containers, networks, images and volumes
+ docker compose -f docker-compose.dev.yml down
+ ```
+
+3. Add or edit MDX files in the `/pages` directory, or add React components in the `/components` directory.
- View the [**nextra**](https://nextra.site/docs) (docs-theme) documentation for more information on using nextra to add content.
+## Available Scripts
+
+### `npm run dev`
+
+Starts the local NextJS / Nextra app in development mode on localhost.
+
+### `npm run build`
+
+Builds the static site into the `"out"` directory.
+
+### `npm run lint`
+
+Checks lint errors.
+
+### `npm run lint:fix`
+
+Fixes lint errors.
+
@acaptutorials
20240806
diff --git a/docker-compose.dev.yml b/docker-compose.dev.yml
new file mode 100644
index 00000000..7568bac4
--- /dev/null
+++ b/docker-compose.dev.yml
@@ -0,0 +1,25 @@
+version: "3"
+services:
+ # NextJS v13 app running on development mode
+ acaptutorials.github.io-dev:
+ container_name: acaptutorials.github.io-dev
+ image: acaptutorials.github.io:dev
+ env_file:
+ - ./docs/.env
+ build:
+ context: ./docs
+ dockerfile: Dockerfile
+ target: development
+ networks:
+ - acaptutorials.github.io-dev
+ volumes:
+ - ./docs:/opt/docs
+ - /opt/docs/node_modules
+ - /opt/docs/.next
+ ports:
+ - "3000:3000"
+
+networks:
+ acaptutorials.github.io-dev:
+ name: acaptutorials.github.io-dev
+ external: false
diff --git a/docker-compose.prod.yml b/docker-compose.prod.yml
new file mode 100644
index 00000000..e43183c1
--- /dev/null
+++ b/docker-compose.prod.yml
@@ -0,0 +1,22 @@
+version: "3"
+services:
+ # NextJS exported app running on an nginx webserver
+ acaptutorials.github.io-prod:
+ container_name: acaptutorials.github.io-prod
+ image: acaptutorials.github.io:prod
+ restart: always
+ env_file:
+ - ./docs/.env
+ build:
+ context: ./docs
+ dockerfile: Dockerfile
+ target: production
+ networks:
+ - acaptutorials.github.io-prod
+ ports:
+ - "3000:3000"
+
+networks:
+ acaptutorials.github.io-prod:
+ name: acaptutorials.github.io-prod
+ external: false
diff --git a/docs/.dockerignore b/docs/.dockerignore
new file mode 100644
index 00000000..12eb063e
--- /dev/null
+++ b/docs/.dockerignore
@@ -0,0 +1,6 @@
+.git
+.gitignore
+node_modules
+npm-debug.log
+Dockerfile
+.dockerignore
diff --git a/docs/.env.example b/docs/.env.example
index eb4eee04..40df7584 100644
--- a/docs/.env.example
+++ b/docs/.env.example
@@ -2,4 +2,6 @@ RELEASE_VERSION=0.0.0-alpha.0
RELEASE_PAGE=https://github.com///releases/tag/0.0.0-alpha.0
COMMIT_ID=123456
OPENGRAPH_IMAGE_URL=https:///banner.png
-BASE_URL=https://localhost:3000
\ No newline at end of file
+BASE_URL=https://localhost:3000
+# Uncomment these 2 CHOKIDAR lines if using Docker Desktop and WSL2 on Windows OS
+# WATCHPACK_POLLING=true
\ No newline at end of file
diff --git a/docs/Dockerfile b/docs/Dockerfile
new file mode 100644
index 00000000..94c8aa0e
--- /dev/null
+++ b/docs/Dockerfile
@@ -0,0 +1,29 @@
+FROM node:20.15.0-alpine as base
+RUN mkdir -p /opt/docs
+WORKDIR /opt/docs
+RUN adduser -S client
+RUN chown -R client /opt/docs
+COPY package*.json ./
+
+# BUILD TARGET
+FROM base as build
+RUN npm install && npm cache clean --force
+COPY . ./
+RUN npm run export
+USER client
+
+# DEVELOPMENT CLIENT PROFILE
+FROM base as development
+ENV NODE_ENV=development
+RUN npm install && npm cache clean --force
+COPY . ./
+EXPOSE 3000
+CMD ["npm", "run", "dev"]
+
+# PRODUCTION CLIENT PROFILE
+FROM nginx:1.22.0-alpine as production
+COPY --from=build /opt/docs/out /usr/share/nginx/html
+RUN rm /etc/nginx/conf.d/default.conf
+COPY config/nginx/nginx.conf /etc/nginx/conf.d
+EXPOSE 3000
+CMD ["nginx", "-g", "daemon off;"]
diff --git a/docs/config/nginx/nginx.conf b/docs/config/nginx/nginx.conf
new file mode 100644
index 00000000..017dc104
--- /dev/null
+++ b/docs/config/nginx/nginx.conf
@@ -0,0 +1,29 @@
+# Minimal nginx configuration for running locally in containers
+server {
+ listen 3000;
+
+ root /usr/share/nginx/html;
+ include /etc/nginx/mime.types;
+ index index.html index.html;
+
+ server_name localhost;
+ server_tokens off;
+
+ # Rewrite all React URLs/routes to index.html
+ # location / {
+ # try_files $uri $uri/ /index.html =404;
+ # }
+
+ # Reverse proxy to the backend API server
+ # Requires the backend service running on a container named 'http://acaptutorials.github.io-prod'
+ # location /api {
+ # proxy_pass http://acaptutorials.github.io-prod:3001;
+ # proxy_set_header Host $host;
+ # }
+
+ # Other error pages
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+}
diff --git a/docs/config/nginx/nginx.full.conf b/docs/config/nginx/nginx.full.conf
new file mode 100644
index 00000000..132efa63
--- /dev/null
+++ b/docs/config/nginx/nginx.full.conf
@@ -0,0 +1,103 @@
+# Full nginx configuration with SSL certificate for nginx running on host machine
+# Requires a registered domain name, letsencrypt SSL certificates
+# and local client/server apps (running in containers or manually installed on host)
+
+server {
+ listen 80;
+ listen [::]:80;
+ server_name www.;
+ return 301 https://$request_uri;
+}
+
+server {
+ listen 80;
+ listen [::]:80;
+ server_name ;
+ return 301 https://$request_uri;
+}
+
+server {
+ listen 443 ssl;
+ server_name www.;
+ ssl_certificate /etc/letsencrypt/live//fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live//privkey.pem;
+ return 301 https://$request_uri;
+}
+
+server {
+ listen 443 ssl http2;
+ listen [::]:443 ssl http2;
+
+ server_name ;
+ server_tokens off;
+
+ # Available methods
+ add_header Allow 'GET, POST, PATCH, DELETE, HEAD' always;
+ add_header X-XSS-Protection '1; mode=block';
+
+ if ( $request_method !~ ^(GET|POST|PATCH|DELETE|HEAD)$ ) {
+ return 405;
+ }
+
+ ssl_certificate /etc/letsencrypt/live//fullchain.pem;
+ ssl_certificate_key /etc/letsencrypt/live//privkey.pem;
+
+ ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
+ ssl_prefer_server_ciphers on;
+ ssl_ciphers 'EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH';
+ ssl_dhparam '/etc/pki/nginx/dhparams.pem';
+
+ add_header Strict-Transport-Security 'max-age=63072000; includeSubDomains' always;
+
+ # gzip comppression settings
+ gzip on;
+ gzip_disable 'msie6';
+
+ gzip_vary on;
+ gzip_proxied any;
+ gzip_comp_level 6;
+ gzip_buffers 16 8k;
+ gzip_http_version 1.1;
+ gzip_min_length 0;
+ gzip_types text/plain application/javascript text/css application/json application/x-javascript text/xml application/xml application/xml+rss text/javascript application/vnd.ms-fontobject application/x-font-ttf font/opentype;
+
+ # Reverse proxy to the client website
+ # Requires the client service running on http://:3000 (from a container or manually installed on host)
+ location / {
+ proxy_pass http://:3000;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_cache_bypass $http_upgrade;
+
+ # For websockets
+ proxy_http_version 1.1;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_read_timeout 600s;
+ }
+
+ # Reverse proxy to the backend API server
+ # Requires the backend service running on http://:3001 (from a container or manually installed on host)
+ location /api {
+ proxy_pass http://:3001;
+ proxy_set_header Host $host;
+ proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
+ proxy_set_header X-Forwarded-Proto $scheme;
+ proxy_set_header X-Real-IP $remote_addr;
+ proxy_cache_bypass $http_upgrade;
+
+ # For websockets
+ proxy_http_version 1.1;
+ proxy_set_header Connection 'upgrade';
+ proxy_set_header Upgrade $http_upgrade;
+ proxy_read_timeout 600s;
+ }
+
+ # Other error pages
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+}
diff --git a/scripts/docker-cleanup.sh b/scripts/docker-cleanup.sh
new file mode 100644
index 00000000..f66b53bf
--- /dev/null
+++ b/scripts/docker-cleanup.sh
@@ -0,0 +1,10 @@
+#!/bin/bash
+
+# Stops and deletes ALL Docker resources
+docker image prune
+docker rmi $(docker images -a -q)
+docker stop $(docker ps -a -q)
+docker rm $(docker ps -a -q)
+docker system prune -f
+docker system prune -a
+docker volume prune -f