Skip to content

Commit

Permalink
Merge pull request #64 from weaponsforge/dev
Browse files Browse the repository at this point in the history
v2.0.1
  • Loading branch information
weaponsforge authored Jul 14, 2024
2 parents 9a75a6e + 43bca2e commit d2654be
Show file tree
Hide file tree
Showing 17 changed files with 387 additions and 12 deletions.
7 changes: 7 additions & 0 deletions .dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
.git
.gitignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.vercel
69 changes: 68 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,72 @@ todo-next is a regular TO-DO notes listing app aimed for testing using NextJS an

2. Read the instructions on the **README** files inside the `/server` and `/client` directories for more information on configuring and using the client and backend.

## Installation Using Docker

We can use Docker to run dockerized client and server apps for local development and production mode. The following methods require Docker and Docker compose correctly installed and set up on your development machine.

### Docker Dependencies

The following dependencies are used to build and run the image. Please feel feel free to use other OS and versions as needed.

1. Ubuntu 22.04.1
- Docker version 23.0.1, build a5eeb1
- Docker Compose v2.16.0
2. Microsoft Windows 10 Pro
- version 10.0.19045 Build 19045
- Docker Desktop
- Docker Compose version v2.27.1-desktop.1
- Docker Engine version 26.1.4, build 5650f9b

### Docker for Localhost Development

1. Set up the environment variables for the `/client` and `/server` directories.
- Visit the `client/README.md` and `server/README.md` files for more information.
- Take note of the `.env` variables setup for Windows and Linux to enable hot reload.
2. Verify that ports 3000 and 3001 are free because the client and server containers will use these ports.
3. Stop current-running containers, if any.
```
docker compose -f docker-compose.dev.yml down
docker compose -f docker-compose.prod.yml up
```
4. Stop and delete all docker instances for a fresh start.
- > **NOTE:** Running this script will delete all docker images, containers, volumes, and networks. Run this script if you feel like everything is piling but do not proceed if you have important work on other running Docker containers.
- ```
sudo chmod u+x scripts/docker-cleanup.sh
./scripts/docker-cleanup.sh
# Answer all proceeding prompts
```
5. Edit any of the files under the `/client` or `/server` directory after running step no. 2.2 and wait for their live reload on `http://localhost:3000` (client) and `http://localhost:3001` (server).
```
# 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
```
### Docker for Production Deployment
The following docker-compose commands build a small client image targeted for creating optimized dockerized apps running on self-managed production servers. An Nginx service serves the frontend client on port 3000. Hot reload is NOT available when editing source codes from the `/client` and `/server` directories.
1. Follow step numbers 1 - 4 in the [Docker for Localhost Development](#docker-for-localhost-development) section.
2. Build the client and server containers for production deployment.<br>
- > **NOTE:** Run this step only once or as needed when housekeeping docker images or if there are new source code updates in the **/client** or **/server** directories.
- `docker compose -f docker-compose.prod.yml build`
3. Load the production mode apps on `http://localhost:3000` (client) and `http://localhost:3001` (server) after running step no. 3.1.
```
# 3.1. Create and start the production client and server containers.
docker compose -f docker-compose.prod.yml up

# 3.2. Stop and remove the production containers, networks, images and volumes
docker compose -f docker-compose.prod.yml down
```
@weaponsforge<br>
20220820
20220820<br>
20240714
6 changes: 6 additions & 0 deletions client/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
.git
.gitignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
6 changes: 4 additions & 2 deletions client/.env.example
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
NEXT_PUBLIC_BASE_PATH=''
BASE_API_URL=http://localhost:3001/api
NEXT_PUBLIC_BASE_PATH=''
BASE_API_URL=http://localhost:3001/api
# Uncomment this line if using WSL2 on Windows OS
# WATCHPACK_POLLING=true
2 changes: 1 addition & 1 deletion client/.eslintrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,6 @@
"semi": ["error", "never"],
"no-unused-vars": "error",
"no-undef": "error",
"no-console": 2
"no-console": ["error", { "allow": ["error"] }]
}
}
30 changes: 30 additions & 0 deletions client/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
FROM node:18.14.2-alpine as base
RUN mkdir -p /opt/client
WORKDIR /opt/client
RUN adduser -S client
RUN chown -R client /opt/client
COPY package*.json ./

# BUILD TARGET
FROM base as build
RUN npm install && npm cache clean --force
COPY . ./
# RUN mkdir /opt/out && chown -R client /opt/out
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/client/out /usr/share/nginx/html
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx/nginx.conf /etc/nginx/conf.d
EXPOSE 3000
CMD ["nginx", "-g", "daemon off;"]
3 changes: 2 additions & 1 deletion client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,8 @@ This directory will contain the web user interfaces for interacting with the Tod
| Variable Name | Description |
| --------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| NEXT_PUBLIC_BASE_PATH | Directory name of assets and media that NextJS uses for the app.<br><br>Set its value to blank `''` when working on development mode in localhost.<br>Set its value to the sub-directory name where the exported NextJS app is to be deployed, i.e. `/<YOUR_REPOSITORY_NAME>` when deploying on a repository (sub-directory) of a root GitHub Pages site. |
| BASE_API_URL | Base URL of the Todo CRUD API from the `/server` directory. |
| BASE_API_URL | Base URL of the Todo CRUD API from the `/server` directory. |
| WATCHPACK_POLLING | Enables hot reload on NextJS apps (tested on NextJS v13.2.1) running inside Docker containers on a Windows host. Set it to `true` if running Docker Desktop with WSL2 on a Windows OS.|

## Usage

Expand Down
29 changes: 29 additions & 0 deletions client/nginx/nginx.conf
Original file line number Diff line number Diff line change
@@ -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 'todo-server-prod'
location /api {
proxy_pass http://todo-server-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;
}
}
103 changes: 103 additions & 0 deletions client/nginx/nginx.full.conf
Original file line number Diff line number Diff line change
@@ -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.<YOUR.DOMAIN.COM.HERE>;
return 301 https://<YOUR.DOMAIN.COM.HERE>$request_uri;
}

server {
listen 80;
listen [::]:80;
server_name <YOUR.DOMAIN.COM.HERE>;
return 301 https://<YOUR.DOMAIN.COM.HERE>$request_uri;
}

server {
listen 443 ssl;
server_name www.<YOUR.DOMAIN.COM.HERE>;
ssl_certificate /etc/letsencrypt/live/<YOUR.DOMAIN.COM.HERE>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<YOUR.DOMAIN.COM.HERE>/privkey.pem;
return 301 https://<YOUR.DOMAIN.COM.HERE>$request_uri;
}

server {
listen 443 ssl http2;
listen [::]:443 ssl http2;

server_name <YOUR.DOMAIN.COM.HERE>;
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/<YOUR.DOMAIN.COM.HERE>/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/<YOUR.DOMAIN.COM.HERE>/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://<MACHINE_PRIVATE_IP>:3000 (from a container or manually installed on host)
location / {
proxy_pass http://<MACHINE_PRIVATE_IP>: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://<MACHINE_PRIVATE_IP>:3001 (from a container or manually installed on host)
location /api {
proxy_pass http://<MACHINE_PRIVATE_IP>: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;
}
}
42 changes: 42 additions & 0 deletions docker-compose.dev.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
version: "3"
services:
# NextJS v13 app running on development mode
todo-client-dev:
container_name: todo-client-dev
image: weaponsforge/todo-client:dev
env_file:
- ./client/.env
build:
context: ./client
dockerfile: Dockerfile
target: development
networks:
- todo-next-dev
volumes:
- ./client:/opt/client
- /opt/client/node_modules
- /opt/client/.next
ports:
- "3000:3000"

# Express server running in development mode
todo-server-dev:
container_name: todo-server-dev
image: weaponsforge/todo-server:dev
env_file:
- ./server/.env
build:
context: ./server
dockerfile: ./Dockerfile
target: development
networks:
- todo-next-dev
volumes:
- ./server/src:/opt/server/src
ports:
- "3001:3001"

networks:
todo-next-dev:
name: todo-next-dev
external: false
36 changes: 36 additions & 0 deletions docker-compose.prod.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
version: "3"
services:
# NextJS exported app running on an nginx webserver
todo-client-prod:
container_name: todo-client-prod
image: weaponsforge/todo-client:latest
restart: always
build:
context: ./client
dockerfile: Dockerfile
target: production
networks:
- todo-next-prod
ports:
- "3000:3000"

# Express web server app running in production mode
todo-server-prod:
container_name: todo-server-prod
image: weaponsforge/todo-server:latest
restart: always
env_file:
- ./server/.env
build:
context: ./server
dockerfile: Dockerfile
target: production
networks:
- todo-next-prod
ports:
- "3001:3001"

networks:
todo-next-prod:
name: todo-next-prod
external: false
10 changes: 10 additions & 0 deletions scripts/docker-cleanup.sh
Original file line number Diff line number Diff line change
@@ -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
8 changes: 8 additions & 0 deletions server/.dockerignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
.git
.gitignore
node_modules
npm-debug.log
Dockerfile
.dockerignore
.env
.vercel
5 changes: 4 additions & 1 deletion server/.env.example
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
ALLOWED_ORIGINS=http://localhost:3000
ALLOW_CORS=0
ALLOW_CORS=1
API_RATE_LIMIT=100
API_WINDOW_MS_MINUTES=15
MONGO_URI=mongodb://localhost/todo-next
DEPLOYMENT_PLATFORM=regular
# Uncomment these 2 CHOKIDAR lines if using Docker Desktop and WSL2 on Windows OS
# CHOKIDAR_USEPOLLING=true
# CHOKIDAR_INTERVAL=1000
Loading

0 comments on commit d2654be

Please sign in to comment.