Loading doc/251112_01_react_tornado_blog_stage4_enhanced.md 0 → 100644 +305 −0 Original line number Diff line number Diff line # React Tornado Blog Demo (Stage 4) **Development and deployment of multiple isolated student apps served on the same machine** via one centralized, containerized Nginx service. Accessible under the Tinkering Club domain: ➡️ https://aidaho-tinkering-club.uni-hohenheim.de Replication of the Tornado Blog demo [see GitHub Tornado Blog Dem](https://github.com/tornadoweb/tornado/blob/master/demos/blog/blog.py) using: - Python **Tornado** (backend) - **PostgreSQL** (database) - **React** (frontend) Source branch: `react-tornado-blog-stage-04` Repository: [aidaho-tinkering-club-web-app](https://aidaho-edu.uni-hohenheim.de/gitlab/mmoessler/aidaho-tinkering-club-web-app) See Example deployment: - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/) - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/) --- ## 🧱 Development Setup ### `docker-compose.yml` ```yaml services: react-frontend: build: context: ./react-frontend dockerfile: Dockerfile volumes: - ./react-frontend/my-react-app:/app ports: - "3000:3000" stdin_open: true tty: true environment: - CHOKIDAR_USEPOLLING=true # Support live reloading on mounted volumes command: ["npm", "start"] depends_on: - tornado-backend networks: - app-network tornado-backend: build: ./tornado-backend links: - postgres ports: - "8888:8888" volumes: - ./tornado-backend/blog.py:/usr/src/app/blog.py command: --db_host=postgres networks: - app-network postgres: image: postgres:10.3 environment: POSTGRES_USER: blog POSTGRES_PASSWORD: blog POSTGRES_DB: blog ports: - "5432:5432" volumes: - ./pg_data:/var/lib/postgresql/data:rw networks: - app-network adminer: image: adminer ports: - 8080:8080 networks: - app-network networks: app-network: driver: bridge ``` --- ## 🚀 Deployment ### Overview Each student app consists of: - One **backend container** (`tornado-backend` + `postgres`) - A **frontend React build** - Served by one shared **Nginx reverse proxy** Each backend joins a **shared external network**: `nginx-net`. --- ### 🧩 Example Backend Configuration Example for **blog demo 01**: ```yaml version: '3.8' services: tornado-backend: build: ./tornado-backend command: --db_host=postgres depends_on: - postgres expose: - "8888" restart: unless-stopped networks: blog-demo-01-net: nginx-net: aliases: - blog-demo-01-backend postgres: image: postgres:10.3 environment: POSTGRES_USER: blog POSTGRES_PASSWORD: blog POSTGRES_DB: blog volumes: - ./pg_data:/var/lib/postgresql/data:rw restart: unless-stopped networks: - blog-demo-01-net networks: blog-demo-01-net: driver: bridge nginx-net: external: true ``` --- ### 🧠 Student Deployment Workflow 1. **Clone your app repository** ```bash cd /srv/projects/ git clone https://aidaho-edu.uni-hohenheim.de/gitlab/mmoessler/aidaho-tinkering-club-web-app my-app ``` 2. **Create your own docker-compose.prod.yml** based on the example above. 3. **Connect your backend container** to the shared Nginx network: ```bash docker network connect nginx-net <your_backend_container_name> ``` 4. **Request a route from the administrator**. Provide: - Your app name (e.g., `blog-demo-03`) - Desired path (`/blog-demo-03/`) - Backend alias (`blog-demo-03-backend`) 5. **Build your React frontend** ```bash docker run -it --rm -v $(pwd)/react-frontend/my-react-app:/app -w /app node:20-alpine sh npm install npm run build ``` 6. **Copy the built files** to the shared nginx assets directory: ```bash cp -rf ./react-frontend/my-react-app/build/* /srv/nginx/assets/static/blog-demo-03/ ``` 7. **Restart your backend container:** ```bash docker-compose -f docker-compose.prod.yml up -d --build ``` --- ## 🧭 Frontend Configuration In `react-frontend/my-react-app`: | File | Setting | Example | |------|----------|----------| | `package.json` | `"homepage"` | `"/blog-demo-03"` | | `src/App.js` | `<Router basename>` | `"/blog-demo-03"` | | `src/config.js` | `frontendBaseUrl`, `backendBaseUrl` | Frontend: `https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-03` <br> Backend: `https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-03/api` | --- ## 🌐 Central Nginx Reverse Proxy ### docker-compose (shared) ```yaml services: nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./assets/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro - ./assets/nginx/proxy.conf:/etc/nginx/proxy.conf:ro - ./assets/static:/usr/share/nginx/html:rw - /srv/startpage:/srv/startpage:ro - ./assets/nginx/ssl:/etc/nginx/ssl:ro restart: always networks: - nginx-net networks: nginx-net: external: true ``` --- ### nginx configuration ```nginx include /etc/nginx/proxy.conf; server { listen 80; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name aidaho-tinkering-club.uni-hohenheim.de; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Security Headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Permissions-Policy "geolocation=(), microphone=()" always; # ---- App: blog-demo-01 ---- location /blog-demo-01/api/ { proxy_pass http://blog-demo-01-backend:8888/api/; include /etc/nginx/proxy.conf; } location /blog-demo-01/ { alias /usr/share/nginx/html/blog-demo-01/; try_files $uri $uri/ /blog-demo-01/index.html; } # ---- App: blog-demo-02 ---- location /blog-demo-02/api/ { proxy_pass http://blog-demo-02-backend:8888/api/; include /etc/nginx/proxy.conf; } location /blog-demo-02/ { alias /usr/share/nginx/html/blog-demo-02/; try_files $uri $uri/ /blog-demo-02/index.html; } # ---- Root Start Page ---- location / { root /srv/startpage; index index.html; try_files $uri $uri/ /index.html; } # ---- Static Assets ---- location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { expires 30d; access_log off; } autoindex off; } ``` --- ## ✅ Example URLs - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/) - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/) --- ## 🧩 Optional Enhancements - **Multi-stage Docker builds** for React apps (build → serve). - **Automated SSL** with `letsencrypt-nginx-proxy-companion`. - **Template repository** for new student apps. - Centralized **volume directories** per app (e.g., `/srv/dbs/blog-demo-01/`). --- © 2025 University of Hohenheim — Aidaho Tinkering Club doc/251112_02_react_tornado_blog_stage4_enhanced.md 0 → 100644 +390 −0 Original line number Diff line number Diff line # React Tornado Blog Demo (Stage 4) **Development and deployment of multiple isolated student apps served on the same machine** via one centralized, containerized Nginx service. Accessible under the Tinkering Club domain: ➡️ https://aidaho-tinkering-club.uni-hohenheim.de Replication of the Tornado Blog demo [see GitHub Tornado Blog Dem](https://github.com/tornadoweb/tornado/blob/master/demos/blog/blog.py) using: - Python **Tornado** (backend) - **PostgreSQL** (database) - **React** (frontend) Source branch: `react-tornado-blog-stage-04` Repository: [aidaho-tinkering-club-web-app](https://aidaho-edu.uni-hohenheim.de/gitlab/mmoessler/aidaho-tinkering-club-web-app) See Example deployment: - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/) - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/) --- ## 🧱 Development Setup ### `docker-compose.yml` ```yaml services: react-frontend: build: context: ./react-frontend dockerfile: Dockerfile volumes: - ./react-frontend/my-react-app:/app ports: - "3000:3000" stdin_open: true tty: true environment: - CHOKIDAR_USEPOLLING=true # Support live reloading on mounted volumes command: ["npm", "start"] depends_on: - tornado-backend networks: - app-network tornado-backend: build: ./tornado-backend links: - postgres ports: - "8888:8888" volumes: - ./tornado-backend/blog.py:/usr/src/app/blog.py command: --db_host=postgres networks: - app-network postgres: image: postgres:10.3 environment: POSTGRES_USER: blog POSTGRES_PASSWORD: blog POSTGRES_DB: blog ports: - "5432:5432" volumes: - ./pg_data:/var/lib/postgresql/data:rw networks: - app-network adminer: image: adminer ports: - 8080:8080 networks: - app-network networks: app-network: driver: bridge ``` ```mermaid graph TD subgraph DevMachine["💻 Developer Machine"] subgraph DockerNetwork["Docker Network: app-network"] React["🟦 react-frontend<br>Port 3000<br><small>npm start, live reload</small>"] Tornado["🟧 tornado-backend<br>Port 8888<br><small>Python Tornado server</small>"] Postgres["🟩 postgres<br>Port 5432<br><small>Database (blog)</small>"] Adminer["🟪 adminer<br>Port 8080<br><small>DB UI</small>"] end end %% External user interaction User["🧑💻 Web Browser<br><small>http://localhost:3000</small>"] User -->|HTTP requests| React %% React and Tornado connection React -->|"API calls<br>fetch()"| Tornado Tornado -->|DB queries| Postgres %% Adminer connects to DB Adminer -->|Manage database| Postgres %% Notes classDef svc fill:#f9f9f9,stroke:#bbb,stroke-width:1px,rx:8,ry:8; class React,Tornado,Postgres,Adminer svc; %% Dependencies Tornado -.depends_on.-> Postgres React -.depends_on.-> Tornado ``` --- ## 🚀 Deployment ### Overview Each student app consists of: - One **backend container** (`tornado-backend` + `postgres`) - A **frontend React build** - Served by one shared **Nginx reverse proxy** Each backend joins a **shared external network**: `nginx-net`. --- ### 🧩 Example Backend Configuration Example for **blog demo 01**: ```yaml version: '3.8' services: tornado-backend: build: ./tornado-backend command: --db_host=postgres depends_on: - postgres expose: - "8888" restart: unless-stopped networks: blog-demo-01-net: nginx-net: aliases: - blog-demo-01-backend postgres: image: postgres:10.3 environment: POSTGRES_USER: blog POSTGRES_PASSWORD: blog POSTGRES_DB: blog volumes: - ./pg_data:/var/lib/postgresql/data:rw restart: unless-stopped networks: - blog-demo-01-net networks: blog-demo-01-net: driver: bridge nginx-net: external: true ``` --- ### 🧠 Student Deployment Workflow 1. **Clone your app repository** ```bash cd /srv/projects/ git clone https://aidaho-edu.uni-hohenheim.de/gitlab/mmoessler/aidaho-tinkering-club-web-app my-app ``` 2. **Create your own docker-compose.prod.yml** based on the example above. 3. **Connect your backend container** to the shared Nginx network: ```bash docker network connect nginx-net <your_backend_container_name> ``` 4. **Request a route from the administrator**. Provide: - Your app name (e.g., `blog-demo-03`) - Desired path (`/blog-demo-03/`) - Backend alias (`blog-demo-03-backend`) 5. **Build your React frontend** ```bash docker run -it --rm -v $(pwd)/react-frontend/my-react-app:/app -w /app node:20-alpine sh npm install npm run build ``` 6. **Copy the built files** to the shared nginx assets directory: ```bash cp -rf ./react-frontend/my-react-app/build/* /srv/nginx/assets/static/blog-demo-03/ ``` 7. **Restart your backend container:** ```bash docker-compose -f docker-compose.prod.yml up -d --build ``` --- ## 🧭 Frontend Configuration In `react-frontend/my-react-app`: | File | Setting | Example | |------|----------|----------| | `package.json` | `"homepage"` | `"/blog-demo-03"` | | `src/App.js` | `<Router basename>` | `"/blog-demo-03"` | | `src/config.js` | `frontendBaseUrl`, `backendBaseUrl` | Frontend: `https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-03` <br> Backend: `https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-03/api` | --- ## 🌐 Central Nginx Reverse Proxy ### docker-compose (shared) ```yaml services: nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./assets/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro - ./assets/nginx/proxy.conf:/etc/nginx/proxy.conf:ro - ./assets/static:/usr/share/nginx/html:rw - /srv/startpage:/srv/startpage:ro - ./assets/nginx/ssl:/etc/nginx/ssl:ro restart: always networks: - nginx-net networks: nginx-net: external: true ``` --- ### nginx configuration ```nginx include /etc/nginx/proxy.conf; server { listen 80; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name aidaho-tinkering-club.uni-hohenheim.de; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Security Headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Permissions-Policy "geolocation=(), microphone=()" always; # ---- App: blog-demo-01 ---- location /blog-demo-01/api/ { proxy_pass http://blog-demo-01-backend:8888/api/; include /etc/nginx/proxy.conf; } location /blog-demo-01/ { alias /usr/share/nginx/html/blog-demo-01/; try_files $uri $uri/ /blog-demo-01/index.html; } # ---- App: blog-demo-02 ---- location /blog-demo-02/api/ { proxy_pass http://blog-demo-02-backend:8888/api/; include /etc/nginx/proxy.conf; } location /blog-demo-02/ { alias /usr/share/nginx/html/blog-demo-02/; try_files $uri $uri/ /blog-demo-02/index.html; } # ---- Root Start Page ---- location / { root /srv/startpage; index index.html; try_files $uri $uri/ /index.html; } # ---- Static Assets ---- location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { expires 30d; access_log off; } autoindex off; } ``` --- ## Visualization ```mermaid graph TD %% Internet user User["🧑💻 User Browser<br><small>https://aidaho-tinkering-club.uni-hohenheim.de</small>"] %% Central reverse proxy Nginx["🌐 Central Nginx Reverse Proxy<br><small>Container on nginx-net<br>Ports 80/443</small>"] %% External connection User -->|"HTTPS (443)"| Nginx %% App 01 group subgraph App01["🧩 Blog Demo 01"] FE01["🟦 Built React App<br><small>/usr/share/nginx/html/blog-demo-01</small>"] BE01["🟧 Tornado Backend<br><small>blog-demo-01-backend:8888</small>"] DB01["🟩 Postgres DB<br><small>blog-demo-01-net</small>"] FE01 -->|"API calls /blog-demo-01/api/"| BE01 BE01 -->|"SQL queries"| DB01 end %% App 02 group subgraph App02["🧩 Blog Demo 02"] FE02["🟦 Built React App<br><small>/usr/share/nginx/html/blog-demo-02</small>"] BE02["🟧 Tornado Backend<br><small>blog-demo-02-backend:8888</small>"] DB02["🟩 Postgres DB<br><small>blog-demo-02-net</small>"] FE02 -->|"API calls /blog-demo-02/api/"| BE02 BE02 -->|"SQL queries"| DB02 end %% Nginx routing Nginx -->|"/blog-demo-01/ → static frontend"| FE01 Nginx -->|"/blog-demo-01/api/ → proxy_pass"| BE01 Nginx -->|"/blog-demo-02/ → static frontend"| FE02 Nginx -->|"/blog-demo-02/api/ → proxy_pass"| BE02 %% Start page Nginx -->|"/ → startpage (/srv/startpage)"| StartPage["📄 Static Start Page"] %% Style definitions classDef box fill:#f9f9f9,stroke:#bbb,stroke-width:1px,rx:8,ry:8; class Nginx,FE01,FE02,BE01,BE02,DB01,DB02,StartPage box; %% Networks Nginx --- FE01 & FE02 & BE01 & BE02:::box BE01 --- DB01 BE02 --- DB02 ``` ## ✅ Example URLs - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/) - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/) --- ## 🧩 Optional Enhancements - **Multi-stage Docker builds** for React apps (build → serve). - **Automated SSL** with `letsencrypt-nginx-proxy-companion`. - **Template repository** for new student apps. - Centralized **volume directories** per app (e.g., `/srv/dbs/blog-demo-01/`). --- © 2025 University of Hohenheim — Aidaho Tinkering Club doc/react_tornado_blog_stage_04.md +325 −15 File changed.Preview size limit exceeded, changes collapsed. Show changes page/aidatho_tc_web_app_presentation.html +4 −6 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ <meta charset="utf-8"> <meta name="generator" content="pandoc"> <meta name="author" content="Markus Mößler"> <meta name="dcterms.date" content="2025-04-30"> <meta name="dcterms.date" content="2025-11-12"> <title>AIDAHO Tinkering Club: Web App</title> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> Loading @@ -31,9 +31,8 @@ } .display.math{display: block; text-align: center; margin: 0.5rem auto;} /* CSS for syntax highlighting */ html { -webkit-text-size-adjust: 100%; } pre > code.sourceCode { white-space: pre; position: relative; } pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } pre > code.sourceCode > span { line-height: 1.25; } pre > code.sourceCode > span:empty { height: 1.2em; } .sourceCode { overflow: visible; } code.sourceCode > span { color: inherit; text-decoration: inherit; } Loading Loading @@ -99,7 +98,6 @@ div.csl-bib-body { } div.csl-entry { clear: both; margin-bottom: 0em; } .hanging-indent div.csl-entry { margin-left:2em; Loading Loading @@ -132,7 +130,7 @@ <section id="title-slide"> <h1 class="title">AIDAHO Tinkering Club:<br>Web App</h1> <p class="author">Markus Mößler</p> <p class="date">April 30, 2025</p> <p class="date">November 12, 2025</p> </section> <section> Loading Loading @@ -605,7 +603,7 @@ graph TD <section id="references" class="title-slide slide level1"> <h1>References</h1> <div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" data-line-spacing="2" role="list"> data-line-spacing="2" role="list"> <div id="ref-Dory2012Introduction" class="csl-entry" role="listitem"> Dory, M., Parrish, A., & Berg, B. (2012). <em>Introduction to tornado: Modern web applications with python</em>. O’Reilly Media. Loading page/aidatho_tc_web_app_presentation.md +14 −5 Original line number Diff line number Diff line --- title: "AIDAHO Tinkering Club:<br>Web App" author: "Markus Mößler" date: "April 30, 2025" date: "November 12, 2025" # date: "April 30, 2025" # date: "April 15, 2025" # date: "December 20, 2024" --- Loading Loading @@ -338,7 +339,7 @@ More Modern Alternatives: --- ## Containerization {.small-header} ## Containerization (Development) {.small-header} <div style="font-size: 20pt; text-align: left"> Loading @@ -352,7 +353,7 @@ More Modern Alternatives: - The backend connects directly to the database container - Hot-reloading enables fast development and testing **Deployment** <!-- **Deployment** - React app is built into static files and served as static files - Containers: Loading @@ -360,13 +361,13 @@ More Modern Alternatives: - PostgreSQL database - (Optional) Add a reverse proxy: - Use Nginx to route requests between users and backend services - Improves performance, SSL termination, and routing flexibility - Improves performance, SSL termination, and routing flexibility --> </div> --- ## Visualization {.very-small-header} ## Visualization (Development) {.very-small-header} <pre class="mermaid mermaid-container"> graph TD Loading @@ -381,6 +382,14 @@ graph TD TornadoDev -->|Queries| PostgreSQL </pre> <!-- # Deployment ... --> # References ::: {#refs} Loading Loading
doc/251112_01_react_tornado_blog_stage4_enhanced.md 0 → 100644 +305 −0 Original line number Diff line number Diff line # React Tornado Blog Demo (Stage 4) **Development and deployment of multiple isolated student apps served on the same machine** via one centralized, containerized Nginx service. Accessible under the Tinkering Club domain: ➡️ https://aidaho-tinkering-club.uni-hohenheim.de Replication of the Tornado Blog demo [see GitHub Tornado Blog Dem](https://github.com/tornadoweb/tornado/blob/master/demos/blog/blog.py) using: - Python **Tornado** (backend) - **PostgreSQL** (database) - **React** (frontend) Source branch: `react-tornado-blog-stage-04` Repository: [aidaho-tinkering-club-web-app](https://aidaho-edu.uni-hohenheim.de/gitlab/mmoessler/aidaho-tinkering-club-web-app) See Example deployment: - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/) - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/) --- ## 🧱 Development Setup ### `docker-compose.yml` ```yaml services: react-frontend: build: context: ./react-frontend dockerfile: Dockerfile volumes: - ./react-frontend/my-react-app:/app ports: - "3000:3000" stdin_open: true tty: true environment: - CHOKIDAR_USEPOLLING=true # Support live reloading on mounted volumes command: ["npm", "start"] depends_on: - tornado-backend networks: - app-network tornado-backend: build: ./tornado-backend links: - postgres ports: - "8888:8888" volumes: - ./tornado-backend/blog.py:/usr/src/app/blog.py command: --db_host=postgres networks: - app-network postgres: image: postgres:10.3 environment: POSTGRES_USER: blog POSTGRES_PASSWORD: blog POSTGRES_DB: blog ports: - "5432:5432" volumes: - ./pg_data:/var/lib/postgresql/data:rw networks: - app-network adminer: image: adminer ports: - 8080:8080 networks: - app-network networks: app-network: driver: bridge ``` --- ## 🚀 Deployment ### Overview Each student app consists of: - One **backend container** (`tornado-backend` + `postgres`) - A **frontend React build** - Served by one shared **Nginx reverse proxy** Each backend joins a **shared external network**: `nginx-net`. --- ### 🧩 Example Backend Configuration Example for **blog demo 01**: ```yaml version: '3.8' services: tornado-backend: build: ./tornado-backend command: --db_host=postgres depends_on: - postgres expose: - "8888" restart: unless-stopped networks: blog-demo-01-net: nginx-net: aliases: - blog-demo-01-backend postgres: image: postgres:10.3 environment: POSTGRES_USER: blog POSTGRES_PASSWORD: blog POSTGRES_DB: blog volumes: - ./pg_data:/var/lib/postgresql/data:rw restart: unless-stopped networks: - blog-demo-01-net networks: blog-demo-01-net: driver: bridge nginx-net: external: true ``` --- ### 🧠 Student Deployment Workflow 1. **Clone your app repository** ```bash cd /srv/projects/ git clone https://aidaho-edu.uni-hohenheim.de/gitlab/mmoessler/aidaho-tinkering-club-web-app my-app ``` 2. **Create your own docker-compose.prod.yml** based on the example above. 3. **Connect your backend container** to the shared Nginx network: ```bash docker network connect nginx-net <your_backend_container_name> ``` 4. **Request a route from the administrator**. Provide: - Your app name (e.g., `blog-demo-03`) - Desired path (`/blog-demo-03/`) - Backend alias (`blog-demo-03-backend`) 5. **Build your React frontend** ```bash docker run -it --rm -v $(pwd)/react-frontend/my-react-app:/app -w /app node:20-alpine sh npm install npm run build ``` 6. **Copy the built files** to the shared nginx assets directory: ```bash cp -rf ./react-frontend/my-react-app/build/* /srv/nginx/assets/static/blog-demo-03/ ``` 7. **Restart your backend container:** ```bash docker-compose -f docker-compose.prod.yml up -d --build ``` --- ## 🧭 Frontend Configuration In `react-frontend/my-react-app`: | File | Setting | Example | |------|----------|----------| | `package.json` | `"homepage"` | `"/blog-demo-03"` | | `src/App.js` | `<Router basename>` | `"/blog-demo-03"` | | `src/config.js` | `frontendBaseUrl`, `backendBaseUrl` | Frontend: `https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-03` <br> Backend: `https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-03/api` | --- ## 🌐 Central Nginx Reverse Proxy ### docker-compose (shared) ```yaml services: nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./assets/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro - ./assets/nginx/proxy.conf:/etc/nginx/proxy.conf:ro - ./assets/static:/usr/share/nginx/html:rw - /srv/startpage:/srv/startpage:ro - ./assets/nginx/ssl:/etc/nginx/ssl:ro restart: always networks: - nginx-net networks: nginx-net: external: true ``` --- ### nginx configuration ```nginx include /etc/nginx/proxy.conf; server { listen 80; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name aidaho-tinkering-club.uni-hohenheim.de; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Security Headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Permissions-Policy "geolocation=(), microphone=()" always; # ---- App: blog-demo-01 ---- location /blog-demo-01/api/ { proxy_pass http://blog-demo-01-backend:8888/api/; include /etc/nginx/proxy.conf; } location /blog-demo-01/ { alias /usr/share/nginx/html/blog-demo-01/; try_files $uri $uri/ /blog-demo-01/index.html; } # ---- App: blog-demo-02 ---- location /blog-demo-02/api/ { proxy_pass http://blog-demo-02-backend:8888/api/; include /etc/nginx/proxy.conf; } location /blog-demo-02/ { alias /usr/share/nginx/html/blog-demo-02/; try_files $uri $uri/ /blog-demo-02/index.html; } # ---- Root Start Page ---- location / { root /srv/startpage; index index.html; try_files $uri $uri/ /index.html; } # ---- Static Assets ---- location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { expires 30d; access_log off; } autoindex off; } ``` --- ## ✅ Example URLs - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/) - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/) --- ## 🧩 Optional Enhancements - **Multi-stage Docker builds** for React apps (build → serve). - **Automated SSL** with `letsencrypt-nginx-proxy-companion`. - **Template repository** for new student apps. - Centralized **volume directories** per app (e.g., `/srv/dbs/blog-demo-01/`). --- © 2025 University of Hohenheim — Aidaho Tinkering Club
doc/251112_02_react_tornado_blog_stage4_enhanced.md 0 → 100644 +390 −0 Original line number Diff line number Diff line # React Tornado Blog Demo (Stage 4) **Development and deployment of multiple isolated student apps served on the same machine** via one centralized, containerized Nginx service. Accessible under the Tinkering Club domain: ➡️ https://aidaho-tinkering-club.uni-hohenheim.de Replication of the Tornado Blog demo [see GitHub Tornado Blog Dem](https://github.com/tornadoweb/tornado/blob/master/demos/blog/blog.py) using: - Python **Tornado** (backend) - **PostgreSQL** (database) - **React** (frontend) Source branch: `react-tornado-blog-stage-04` Repository: [aidaho-tinkering-club-web-app](https://aidaho-edu.uni-hohenheim.de/gitlab/mmoessler/aidaho-tinkering-club-web-app) See Example deployment: - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/) - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/) --- ## 🧱 Development Setup ### `docker-compose.yml` ```yaml services: react-frontend: build: context: ./react-frontend dockerfile: Dockerfile volumes: - ./react-frontend/my-react-app:/app ports: - "3000:3000" stdin_open: true tty: true environment: - CHOKIDAR_USEPOLLING=true # Support live reloading on mounted volumes command: ["npm", "start"] depends_on: - tornado-backend networks: - app-network tornado-backend: build: ./tornado-backend links: - postgres ports: - "8888:8888" volumes: - ./tornado-backend/blog.py:/usr/src/app/blog.py command: --db_host=postgres networks: - app-network postgres: image: postgres:10.3 environment: POSTGRES_USER: blog POSTGRES_PASSWORD: blog POSTGRES_DB: blog ports: - "5432:5432" volumes: - ./pg_data:/var/lib/postgresql/data:rw networks: - app-network adminer: image: adminer ports: - 8080:8080 networks: - app-network networks: app-network: driver: bridge ``` ```mermaid graph TD subgraph DevMachine["💻 Developer Machine"] subgraph DockerNetwork["Docker Network: app-network"] React["🟦 react-frontend<br>Port 3000<br><small>npm start, live reload</small>"] Tornado["🟧 tornado-backend<br>Port 8888<br><small>Python Tornado server</small>"] Postgres["🟩 postgres<br>Port 5432<br><small>Database (blog)</small>"] Adminer["🟪 adminer<br>Port 8080<br><small>DB UI</small>"] end end %% External user interaction User["🧑💻 Web Browser<br><small>http://localhost:3000</small>"] User -->|HTTP requests| React %% React and Tornado connection React -->|"API calls<br>fetch()"| Tornado Tornado -->|DB queries| Postgres %% Adminer connects to DB Adminer -->|Manage database| Postgres %% Notes classDef svc fill:#f9f9f9,stroke:#bbb,stroke-width:1px,rx:8,ry:8; class React,Tornado,Postgres,Adminer svc; %% Dependencies Tornado -.depends_on.-> Postgres React -.depends_on.-> Tornado ``` --- ## 🚀 Deployment ### Overview Each student app consists of: - One **backend container** (`tornado-backend` + `postgres`) - A **frontend React build** - Served by one shared **Nginx reverse proxy** Each backend joins a **shared external network**: `nginx-net`. --- ### 🧩 Example Backend Configuration Example for **blog demo 01**: ```yaml version: '3.8' services: tornado-backend: build: ./tornado-backend command: --db_host=postgres depends_on: - postgres expose: - "8888" restart: unless-stopped networks: blog-demo-01-net: nginx-net: aliases: - blog-demo-01-backend postgres: image: postgres:10.3 environment: POSTGRES_USER: blog POSTGRES_PASSWORD: blog POSTGRES_DB: blog volumes: - ./pg_data:/var/lib/postgresql/data:rw restart: unless-stopped networks: - blog-demo-01-net networks: blog-demo-01-net: driver: bridge nginx-net: external: true ``` --- ### 🧠 Student Deployment Workflow 1. **Clone your app repository** ```bash cd /srv/projects/ git clone https://aidaho-edu.uni-hohenheim.de/gitlab/mmoessler/aidaho-tinkering-club-web-app my-app ``` 2. **Create your own docker-compose.prod.yml** based on the example above. 3. **Connect your backend container** to the shared Nginx network: ```bash docker network connect nginx-net <your_backend_container_name> ``` 4. **Request a route from the administrator**. Provide: - Your app name (e.g., `blog-demo-03`) - Desired path (`/blog-demo-03/`) - Backend alias (`blog-demo-03-backend`) 5. **Build your React frontend** ```bash docker run -it --rm -v $(pwd)/react-frontend/my-react-app:/app -w /app node:20-alpine sh npm install npm run build ``` 6. **Copy the built files** to the shared nginx assets directory: ```bash cp -rf ./react-frontend/my-react-app/build/* /srv/nginx/assets/static/blog-demo-03/ ``` 7. **Restart your backend container:** ```bash docker-compose -f docker-compose.prod.yml up -d --build ``` --- ## 🧭 Frontend Configuration In `react-frontend/my-react-app`: | File | Setting | Example | |------|----------|----------| | `package.json` | `"homepage"` | `"/blog-demo-03"` | | `src/App.js` | `<Router basename>` | `"/blog-demo-03"` | | `src/config.js` | `frontendBaseUrl`, `backendBaseUrl` | Frontend: `https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-03` <br> Backend: `https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-03/api` | --- ## 🌐 Central Nginx Reverse Proxy ### docker-compose (shared) ```yaml services: nginx: image: nginx:alpine ports: - "80:80" - "443:443" volumes: - ./assets/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro - ./assets/nginx/proxy.conf:/etc/nginx/proxy.conf:ro - ./assets/static:/usr/share/nginx/html:rw - /srv/startpage:/srv/startpage:ro - ./assets/nginx/ssl:/etc/nginx/ssl:ro restart: always networks: - nginx-net networks: nginx-net: external: true ``` --- ### nginx configuration ```nginx include /etc/nginx/proxy.conf; server { listen 80; return 301 https://$host$request_uri; } server { listen 443 ssl; server_name aidaho-tinkering-club.uni-hohenheim.de; ssl_certificate /etc/nginx/ssl/fullchain.pem; ssl_certificate_key /etc/nginx/ssl/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; # Security Headers add_header X-Content-Type-Options nosniff; add_header X-Frame-Options DENY; add_header X-XSS-Protection "1; mode=block"; add_header Referrer-Policy "no-referrer-when-downgrade" always; add_header Permissions-Policy "geolocation=(), microphone=()" always; # ---- App: blog-demo-01 ---- location /blog-demo-01/api/ { proxy_pass http://blog-demo-01-backend:8888/api/; include /etc/nginx/proxy.conf; } location /blog-demo-01/ { alias /usr/share/nginx/html/blog-demo-01/; try_files $uri $uri/ /blog-demo-01/index.html; } # ---- App: blog-demo-02 ---- location /blog-demo-02/api/ { proxy_pass http://blog-demo-02-backend:8888/api/; include /etc/nginx/proxy.conf; } location /blog-demo-02/ { alias /usr/share/nginx/html/blog-demo-02/; try_files $uri $uri/ /blog-demo-02/index.html; } # ---- Root Start Page ---- location / { root /srv/startpage; index index.html; try_files $uri $uri/ /index.html; } # ---- Static Assets ---- location ~* \.(js|css|png|jpg|jpeg|gif|svg|ico|woff2?)$ { expires 30d; access_log off; } autoindex off; } ``` --- ## Visualization ```mermaid graph TD %% Internet user User["🧑💻 User Browser<br><small>https://aidaho-tinkering-club.uni-hohenheim.de</small>"] %% Central reverse proxy Nginx["🌐 Central Nginx Reverse Proxy<br><small>Container on nginx-net<br>Ports 80/443</small>"] %% External connection User -->|"HTTPS (443)"| Nginx %% App 01 group subgraph App01["🧩 Blog Demo 01"] FE01["🟦 Built React App<br><small>/usr/share/nginx/html/blog-demo-01</small>"] BE01["🟧 Tornado Backend<br><small>blog-demo-01-backend:8888</small>"] DB01["🟩 Postgres DB<br><small>blog-demo-01-net</small>"] FE01 -->|"API calls /blog-demo-01/api/"| BE01 BE01 -->|"SQL queries"| DB01 end %% App 02 group subgraph App02["🧩 Blog Demo 02"] FE02["🟦 Built React App<br><small>/usr/share/nginx/html/blog-demo-02</small>"] BE02["🟧 Tornado Backend<br><small>blog-demo-02-backend:8888</small>"] DB02["🟩 Postgres DB<br><small>blog-demo-02-net</small>"] FE02 -->|"API calls /blog-demo-02/api/"| BE02 BE02 -->|"SQL queries"| DB02 end %% Nginx routing Nginx -->|"/blog-demo-01/ → static frontend"| FE01 Nginx -->|"/blog-demo-01/api/ → proxy_pass"| BE01 Nginx -->|"/blog-demo-02/ → static frontend"| FE02 Nginx -->|"/blog-demo-02/api/ → proxy_pass"| BE02 %% Start page Nginx -->|"/ → startpage (/srv/startpage)"| StartPage["📄 Static Start Page"] %% Style definitions classDef box fill:#f9f9f9,stroke:#bbb,stroke-width:1px,rx:8,ry:8; class Nginx,FE01,FE02,BE01,BE02,DB01,DB02,StartPage box; %% Networks Nginx --- FE01 & FE02 & BE01 & BE02:::box BE01 --- DB01 BE02 --- DB02 ``` ## ✅ Example URLs - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-01/) - [https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/](https://aidaho-tinkering-club.uni-hohenheim.de/blog-demo-02/) --- ## 🧩 Optional Enhancements - **Multi-stage Docker builds** for React apps (build → serve). - **Automated SSL** with `letsencrypt-nginx-proxy-companion`. - **Template repository** for new student apps. - Centralized **volume directories** per app (e.g., `/srv/dbs/blog-demo-01/`). --- © 2025 University of Hohenheim — Aidaho Tinkering Club
doc/react_tornado_blog_stage_04.md +325 −15 File changed.Preview size limit exceeded, changes collapsed. Show changes
page/aidatho_tc_web_app_presentation.html +4 −6 Original line number Diff line number Diff line Loading @@ -4,7 +4,7 @@ <meta charset="utf-8"> <meta name="generator" content="pandoc"> <meta name="author" content="Markus Mößler"> <meta name="dcterms.date" content="2025-04-30"> <meta name="dcterms.date" content="2025-11-12"> <title>AIDAHO Tinkering Club: Web App</title> <meta name="apple-mobile-web-app-capable" content="yes"> <meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> Loading @@ -31,9 +31,8 @@ } .display.math{display: block; text-align: center; margin: 0.5rem auto;} /* CSS for syntax highlighting */ html { -webkit-text-size-adjust: 100%; } pre > code.sourceCode { white-space: pre; position: relative; } pre > code.sourceCode > span { display: inline-block; line-height: 1.25; } pre > code.sourceCode > span { line-height: 1.25; } pre > code.sourceCode > span:empty { height: 1.2em; } .sourceCode { overflow: visible; } code.sourceCode > span { color: inherit; text-decoration: inherit; } Loading Loading @@ -99,7 +98,6 @@ div.csl-bib-body { } div.csl-entry { clear: both; margin-bottom: 0em; } .hanging-indent div.csl-entry { margin-left:2em; Loading Loading @@ -132,7 +130,7 @@ <section id="title-slide"> <h1 class="title">AIDAHO Tinkering Club:<br>Web App</h1> <p class="author">Markus Mößler</p> <p class="date">April 30, 2025</p> <p class="date">November 12, 2025</p> </section> <section> Loading Loading @@ -605,7 +603,7 @@ graph TD <section id="references" class="title-slide slide level1"> <h1>References</h1> <div id="refs" class="references csl-bib-body hanging-indent" data-entry-spacing="0" data-line-spacing="2" role="list"> data-line-spacing="2" role="list"> <div id="ref-Dory2012Introduction" class="csl-entry" role="listitem"> Dory, M., Parrish, A., & Berg, B. (2012). <em>Introduction to tornado: Modern web applications with python</em>. O’Reilly Media. Loading
page/aidatho_tc_web_app_presentation.md +14 −5 Original line number Diff line number Diff line --- title: "AIDAHO Tinkering Club:<br>Web App" author: "Markus Mößler" date: "April 30, 2025" date: "November 12, 2025" # date: "April 30, 2025" # date: "April 15, 2025" # date: "December 20, 2024" --- Loading Loading @@ -338,7 +339,7 @@ More Modern Alternatives: --- ## Containerization {.small-header} ## Containerization (Development) {.small-header} <div style="font-size: 20pt; text-align: left"> Loading @@ -352,7 +353,7 @@ More Modern Alternatives: - The backend connects directly to the database container - Hot-reloading enables fast development and testing **Deployment** <!-- **Deployment** - React app is built into static files and served as static files - Containers: Loading @@ -360,13 +361,13 @@ More Modern Alternatives: - PostgreSQL database - (Optional) Add a reverse proxy: - Use Nginx to route requests between users and backend services - Improves performance, SSL termination, and routing flexibility - Improves performance, SSL termination, and routing flexibility --> </div> --- ## Visualization {.very-small-header} ## Visualization (Development) {.very-small-header} <pre class="mermaid mermaid-container"> graph TD Loading @@ -381,6 +382,14 @@ graph TD TornadoDev -->|Queries| PostgreSQL </pre> <!-- # Deployment ... --> # References ::: {#refs} Loading