├── .dockerignore ├── package.json ├── k8s ├── basic │ ├── svc-db.yaml │ ├── svc-web.yaml │ ├── pod-db.yaml │ ├── pod-web.yaml │ └── ingress.yaml └── deployments │ ├── deploy-db.yaml │ └── deploy-web.yaml ├── eslint.config.js ├── Dockerfile ├── compose.yaml ├── .github └── workflows │ └── docker-image.yml ├── LICENSE ├── views └── index.ejs ├── .gitignore ├── public └── styles.css ├── app.js └── README.md /.dockerignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | npm-debug.log 3 | Dockerfile 4 | docker-compose.yml 5 | .dockerignore 6 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "dependencies": { 3 | "body-parser": "^1.20.3", 4 | "ejs": "^3.1.10", 5 | "express": "^4.21.1", 6 | "mongoose": "^8.7.1", 7 | "pg": "^8.13.0" 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /k8s/basic/svc-db.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: db 5 | spec: 6 | selector: 7 | app: db 8 | ports: 9 | - protocol: TCP 10 | port: 5432 11 | targetPort: 5432 12 | -------------------------------------------------------------------------------- /k8s/basic/svc-web.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Service 3 | metadata: 4 | name: web 5 | spec: 6 | selector: 7 | app: web 8 | ports: 9 | - protocol: TCP 10 | port: 80 11 | targetPort: 3000 12 | type: ClusterIP 13 | -------------------------------------------------------------------------------- /eslint.config.js: -------------------------------------------------------------------------------- 1 | export default [ 2 | { 3 | files: ['**/*.js'], // Target all JS files 4 | ignores: ['views/**/*.js'], // Ignore all files in the views folder 5 | rules: { 6 | 'semi': ['error', 'always'], 7 | 'quotes': ['error', 'single'], 8 | }, 9 | }, 10 | ]; 11 | -------------------------------------------------------------------------------- /k8s/basic/pod-db.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: db 5 | labels: 6 | app: db 7 | spec: 8 | containers: 9 | - name: postgresql 10 | image: postgres:16.0-bullseye 11 | ports: 12 | - containerPort: 5432 13 | env: 14 | - name: POSTGRES_PASSWORD 15 | value: masterclass100 16 | - name: POSTGRES_USER 17 | value: masterclass 18 | - name: POSTGRES_DB 19 | value: sampledb 20 | -------------------------------------------------------------------------------- /Dockerfile: -------------------------------------------------------------------------------- 1 | # Use Node.js 16 or later 2 | FROM node:20 3 | 4 | # Create app directory 5 | WORKDIR /usr/src/app 6 | 7 | # Copy package.json and package-lock.json first for dependency installation 8 | COPY package*.json ./ 9 | 10 | # Install dependencies 11 | RUN npm install 12 | 13 | # Copy the rest of the application code 14 | COPY . . 15 | 16 | # Expose port 17 | EXPOSE 3000 18 | 19 | # Start the app using nodemon for auto-reload 20 | CMD ["npx", "nodemon", "app.js"] 21 | -------------------------------------------------------------------------------- /k8s/basic/pod-web.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: v1 2 | kind: Pod 3 | metadata: 4 | name: web 5 | labels: 6 | app: web 7 | spec: 8 | containers: 9 | - name: sample-app 10 | image: snkshukla/sample_app:19033dd 11 | ports: 12 | - containerPort: 3000 13 | env: 14 | - name: DB_USER 15 | value: masterclass 16 | - name: DB_PASSWORD 17 | value: masterclass100 18 | - name: DB_NAME 19 | value: sampledb 20 | - name: DB_HOST 21 | value: db # Service name for PostgreSQL 22 | -------------------------------------------------------------------------------- /k8s/deployments/deploy-db.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: db-deployment 5 | spec: 6 | replicas: 1 7 | selector: 8 | matchLabels: 9 | app: db 10 | template: 11 | metadata: 12 | labels: 13 | app: db 14 | spec: 15 | containers: 16 | - name: postgresql 17 | image: postgres:16.0-bullseye 18 | ports: 19 | - containerPort: 5432 20 | env: 21 | - name: POSTGRES_PASSWORD 22 | value: "masterclass100" 23 | - name: POSTGRES_USER 24 | value: "masterclass" 25 | - name: POSTGRES_DB 26 | value: "sampledb" 27 | -------------------------------------------------------------------------------- /k8s/basic/ingress.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: networking.k8s.io/v1 2 | kind: Ingress 3 | metadata: 4 | name: masterclass-ingress 5 | annotations: 6 | cert-manager.io/cluster-issuer: letsencrypt-production 7 | nginx.ingress.kubernetes.io/force-ssl-redirect: "true" 8 | spec: 9 | ingressClassName: nginx 10 | rules: 11 | - host: masterclass.getdevops.services 12 | http: 13 | paths: 14 | - path: / 15 | pathType: Prefix 16 | backend: 17 | service: 18 | name: web 19 | port: 20 | number: 80 21 | tls: 22 | - hosts: 23 | - masterclass.getdevops.services 24 | secretName: web-tls 25 | -------------------------------------------------------------------------------- /compose.yaml: -------------------------------------------------------------------------------- 1 | version: '3' 2 | services: 3 | app: 4 | build: . 5 | ports: 6 | - "3000:3000" 7 | volumes: 8 | - .:/usr/src/app 9 | environment: 10 | - DB_USER=masterclass 11 | - DB_PASSWORD=masterclass100 12 | - DB_NAME=sampledb 13 | - DB_HOST=postgres # Service name for PostgreSQL 14 | depends_on: 15 | - postgres 16 | 17 | postgres: 18 | image: postgres:16.0-bullseye # Use PostgreSQL version 13 19 | environment: 20 | POSTGRES_USER: masterclass 21 | POSTGRES_PASSWORD: masterclass100 22 | POSTGRES_DB: sampledb 23 | ports: 24 | - "5432:5432" 25 | volumes: 26 | - pgdata:/var/lib/postgresql/data 27 | 28 | volumes: 29 | pgdata: 30 | -------------------------------------------------------------------------------- /k8s/deployments/deploy-web.yaml: -------------------------------------------------------------------------------- 1 | apiVersion: apps/v1 2 | kind: Deployment 3 | metadata: 4 | name: web 5 | labels: 6 | app: web 7 | spec: 8 | replicas: 3 # Adjust the number of replicas as needed 9 | selector: 10 | matchLabels: 11 | app: web 12 | template: 13 | metadata: 14 | labels: 15 | app: web 16 | spec: 17 | containers: 18 | - name: sample-app 19 | image: snkshukla/sample_app:19033dd 20 | ports: 21 | - containerPort: 3000 22 | env: 23 | - name: DB_USER 24 | value: masterclass 25 | - name: DB_PASSWORD 26 | value: masterclass100 27 | - name: DB_NAME 28 | value: sampledb 29 | - name: DB_HOST 30 | value: db # Service name for PostgreSQL 31 | -------------------------------------------------------------------------------- /.github/workflows/docker-image.yml: -------------------------------------------------------------------------------- 1 | name: Docker Image CI 2 | 3 | on: 4 | push: 5 | branches: [ "main" ] 6 | pull_request: 7 | branches: [ "main" ] 8 | 9 | jobs: 10 | 11 | build: 12 | 13 | runs-on: ubuntu-latest 14 | 15 | steps: 16 | - uses: actions/checkout@v3 17 | - name: Login to Docker Hub 18 | with: 19 | username: ${{ secrets.DOCKER_USERNAME }} 20 | password: ${{ secrets.DOCKER_PASSWORD }} 21 | uses: docker/login-action@v1 22 | - name: Add SHORT_SHA env property with commit short sha 23 | run: echo "SHORT_SHA=`git rev-parse --short HEAD`" >> $GITHUB_ENV 24 | - name: Build the Docker image 25 | run: docker build --file Dockerfile --tag snkshukla/sample_app:${SHORT_SHA} . 26 | - name: Push the docker image 27 | run: docker push snkshukla/sample_app:${SHORT_SHA} 28 | - name: Print published image tag 29 | run: 'echo "Published image tag is: snkshukla/sample_app:$SHORT_SHA"' 30 | 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2025 [Shubham Shukla] 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /views/index.ejs: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | Fancy App 7 | 8 | 9 | 10 | 11 |
12 |

Welcome to Scaler's Masterclass

13 |
14 |

Today's topics

15 | 24 |
25 |
26 |
27 | 28 | 29 |
30 |
31 |
32 | 33 | 34 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Dependency directories 9 | node_modules/ 10 | jspm_packages/ 11 | 12 | # Optional npm cache directory 13 | .npm 14 | 15 | # Optional eslint cache 16 | .eslintcache 17 | 18 | # Optional REPL history 19 | .node_repl_history 20 | 21 | # dotenv environment variable files 22 | .env 23 | 24 | # Coverage directory for tests 25 | coverage/ 26 | 27 | # nyc test coverage 28 | .nyc_output/ 29 | 30 | # Parcel cache (if you're using it) 31 | .cache/ 32 | 33 | # Dist directory (for build outputs if you have one) 34 | dist/ 35 | 36 | # Optional TypeScript cache 37 | *.tsbuildinfo 38 | 39 | # Optional folders for staging/deployment 40 | .out/ 41 | .next/ 42 | 43 | # Ignore database files 44 | dump.rdb 45 | *.sqlite 46 | *.db 47 | 48 | 49 | # Ignore VSCode and IDE-specific settings 50 | .vscode/ 51 | .idea/ 52 | 53 | # OS-specific files 54 | .DS_Store 55 | Thumbs.db 56 | 57 | # Ignore any local config or secret files 58 | config/local.js 59 | config/*.local.js 60 | config/*.json 61 | config/*.env 62 | 63 | # Optional PM2 log files 64 | .pids 65 | logs/ 66 | 67 | # Sentry files (if you use Sentry) 68 | .sentryclirc 69 | 70 | # Ignore any EJS cached files (if any caching happens) 71 | *.ejs~ 72 | -------------------------------------------------------------------------------- /public/styles.css: -------------------------------------------------------------------------------- 1 | /* Background and container styles */ 2 | body { 3 | margin: 0; 4 | padding: 0; 5 | font-family: 'Arial', sans-serif; 6 | background: url('https://source.unsplash.com/featured/?nature') no-repeat center center fixed; 7 | background-size: cover; 8 | color: white; 9 | } 10 | 11 | .container { 12 | text-align: center; 13 | padding: 50px; 14 | background-color: rgba(0, 0, 0, 0.7); 15 | border-radius: 10px; 16 | margin: 50px auto; 17 | max-width: 600px; 18 | } 19 | 20 | /* Title styles */ 21 | .title { 22 | font-size: 2.5rem; 23 | margin-bottom: 20px; 24 | } 25 | 26 | .subtitle { 27 | font-size: 1.8rem; 28 | margin: 20px 0; 29 | } 30 | 31 | /* List styling */ 32 | ul { 33 | list-style-type: none; 34 | padding: 0; 35 | } 36 | 37 | li { 38 | font-size: 1.5rem; 39 | margin: 10px 0; 40 | padding: 10px; 41 | background-color: rgba(255, 255, 255, 0.2); 42 | border-radius: 5px; 43 | } 44 | 45 | /* Form styling */ 46 | .form-container { 47 | margin-top: 30px; 48 | } 49 | 50 | input[type="text"] { 51 | padding: 10px; 52 | font-size: 1rem; 53 | width: 80%; 54 | border: none; 55 | border-radius: 5px; 56 | outline: none; 57 | } 58 | 59 | .submit-btn { 60 | padding: 10px 20px; 61 | font-size: 1rem; 62 | border: none; 63 | background-color: #28a745; 64 | color: white; 65 | border-radius: 5px; 66 | cursor: pointer; 67 | transition: background-color 0.3s ease; 68 | } 69 | 70 | .submit-btn:hover { 71 | background-color: #218838; 72 | } 73 | -------------------------------------------------------------------------------- /app.js: -------------------------------------------------------------------------------- 1 | const express = require('express'); 2 | const { Pool } = require('pg'); // Import PostgreSQL client 3 | const bodyParser = require('body-parser'); 4 | const path = require('path'); 5 | 6 | const app = express(); 7 | 8 | // Middleware 9 | app.use(bodyParser.urlencoded({ extended: true })); 10 | app.set('view engine', 'ejs'); 11 | app.use(express.static(path.join(__dirname, 'public'))); 12 | 13 | // PostgreSQL connection setup 14 | const pool = new Pool({ 15 | user: process.env.DB_USER, 16 | host: process.env.DB_HOST || 'localhost', 17 | database: process.env.DB_NAME, 18 | password: process.env.DB_PASSWORD, 19 | port: 5432, // Default PostgreSQL port 20 | }); 21 | 22 | // Example query to create a table (if it doesn't exist) 23 | pool.query(` 24 | CREATE TABLE IF NOT EXISTS items ( 25 | id SERIAL PRIMARY KEY, 26 | name VARCHAR(255) NOT NULL 27 | ); 28 | `, (err) => { 29 | if (err) { 30 | console.error('Error creating table:', err); 31 | } else { 32 | console.log('Table created successfully or already exists.'); 33 | } 34 | }); 35 | 36 | // Routes 37 | app.get('/', async (req, res) => { 38 | try { 39 | const result = await pool.query('SELECT * FROM items;'); 40 | res.render('index', { items: result.rows }); 41 | } catch (err) { 42 | console.error('Error fetching topics:', err); 43 | res.status(500).send('Error fetching items.'); 44 | } 45 | }); 46 | 47 | app.post('/add', async (req, res) => { 48 | const itemName = req.body.name; 49 | try { 50 | await pool.query('INSERT INTO items (name) VALUES ($1);', [itemName]); 51 | res.redirect('/'); 52 | } catch (err) { 53 | console.error('Error adding topic:', err); 54 | res.status(500).send('Error adding topic.'); 55 | } 56 | }); 57 | 58 | app.listen(3000, () => { 59 | console.log('Server is running on port 3000'); 60 | }); 61 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Masterclass Sample Application: Docker, Kubernetes, and Microservices 2 | 3 | [![GitHub stars](https://img.shields.io/github/stars/snkshukla/masterclass-sample.svg?style=social)](https://github.com/snkshukla/masterclass-sample/stargazers) 4 | [![GitHub forks](https://img.shields.io/github/forks/snkshukla/masterclass-sample.svg?style=social)](https://github.com/snkshukla/masterclass-sample/network/members) 5 | [![License](https://img.shields.io/github/license/snkshukla/masterclass-sample)](LICENSE) 6 | 7 | Welcome to the **Masterclass Sample Repository** - This repository contains the sample application and configuration files used in the Docker, Kubernetes, and Microservices masterclass at **Scaler**. It's designed to be a practical, hands-on resource that you can use to follow along with the class and explore the concepts in more detail. It provides a real-world example of how to containerize, deploy, and orchestrate a simple Node.js application with a database. 8 | 9 | ## Table of Contents 10 | 11 | 1. [Overview](#Overview) 12 | 2. [Repository Structure](#repository-structure) 13 | 3. [Getting Started](#getting-started) 14 | - [Prerequisites](#Prerequisites) 15 | - [Running Locally with Docker Compose]() 16 | - [Deploying on Kubernetes]() 17 | 4. [Learn More]() 18 | 5. [Blog Series (Deep Dive)](#blog-series-deep-dive) 19 | 6. [Contributing]() 20 | 7. [License]() 21 | 22 | --- 23 | 24 | ## Overview 25 | 26 | This simple Node.js application (with an accompanying database) demonstrates key DevOps concepts like: 27 | 28 | - **SDLC (Software Development Life Cycle)**: Planning → Development → Testing → Deployment → Maintenance 29 | - **Docker**: Packaging applications with Dockerfiles and understanding container fundamentals 30 | - **Docker Compose**: Setting up multi-container environments for local development 31 | - **Kubernetes**: Deploying applications at scale with basic manifests, advanced deployments, and Helm charts 32 | 33 | We’ll use this project to explore everything from setting up local development to creating production-ready Kubernetes clusters. 34 | 35 | --- 36 | 37 | ## Repository Structure 38 | 39 | Here’s a breakdown of the major files and folders: 40 | 41 | ``` 42 | masterclass-sample/ 43 | ├── .dockerignore # Specifies files and directories that Docker should ignore 44 | ├── .github 45 | │ └── workflows # GitHub Actions configurations (CI/CD pipelines, etc.) 46 | ├── .gitignore # Git ignore rules 47 | ├── Dockerfile # Defines how to build the application’s Docker image 48 | ├── LICENSE # Open-source license for this repository 49 | ├── README.md # The file you’re reading now 50 | ├── app.js # Main Node.js application entry point 51 | ├── compose.yaml # Docker Compose configuration file 52 | ├── eslint.config.js # ESLint configuration for consistent coding standards 53 | ├── k8s 54 | │ ├── basic # Basic Kubernetes manifests (e.g., Pod, Service) 55 | │ ├── deployments # Additional/advanced deployment manifests 56 | │ └── helm-chart # Helm chart(s) for packaging and deploying on Kubernetes 57 | ├── package-lock.json # NPM lock file (ensures consistent dependency versions) 58 | ├── package.json # NPM metadata (project dependencies, scripts, etc.) 59 | ├── public 60 | │ └── styles.css # Static CSS for styling the application UI 61 | └── views 62 | └── index.ejs # Template file for server-side rendering 63 | 64 | ``` 65 | 66 | ### Highlights 67 | 68 | - **`Dockerfile`**: Defines the base image and steps required to run the Node.js application. 69 | - **`compose.yaml`**: Illustrates a multi-container setup, including a database, for local development. 70 | - **`k8s/`**: Contains everything Kubernetes-related, including: 71 | - **basic**: Simple resource manifests (Pods, Services). 72 | - **deployments**: More advanced deployments or stateful sets. 73 | - **helm-chart**: Helm-based packaging for easier versioning and reusable deployment configurations. 74 | - **`app.js`**: Node.js application logic to demonstrate a minimal web service. 75 | 76 | --- 77 | 78 | ## Getting Started 79 | 80 | ### Prerequisites 81 | 82 | You’ll need the following installed on your machine: 83 | 84 | - Node.js (optional if you just want to run via Docker) 85 | - Docker 86 | - Docker Compose 87 | - Kubernetes CLI (kubectl) 88 | - Helm (optional) for using Helm charts 89 | 90 | ### Running Locally with Docker Compose 91 | 92 | 1. **Clone this repository**: 93 | 94 | ```bash 95 | git clone https://github.com/snkshukla/masterclass-sample.git 96 | cd masterclass-sample 97 | ``` 98 | 99 | 2. **Build and run the containers**: 100 | 101 | ```bash 102 | docker-compose -f compose.yaml up --build 103 | ``` 104 | 105 | 3. **Access your application**: 106 | 107 | Open your browser and go to: 108 | 109 | ``` 110 | http://localhost:3000 111 | ``` 112 | 113 | You should see the Node.js application up and running, connected to its database (if configured in the Compose file). 114 | 115 | 116 | ### Deploying on Kubernetes 117 | 118 | > Note: Make sure you have a running Kubernetes cluster (either a local tool like minikube, Kind or a cloud provider). 119 | > 120 | 1. **Apply the basic Kubernetes manifests** (in the `k8s/basic` folder): 121 | 122 | ```bash 123 | kubectl apply -f k8s/basic/ 124 | ``` 125 | 126 | 2. **Check the status**: 127 | 128 | ```bash 129 | kubectl get pods 130 | kubectl get services 131 | ``` 132 | 133 | 3. **(Optional) Port Forward** to access the service locally: 134 | 135 | ```bash 136 | kubectl port-forward svc/web 3000:3000 137 | ``` 138 | 139 | 4. **Open your browser** to http://localhost:3000 to see the running application. 140 | 5. **Explore advanced Kubernetes** deployments in the `k8s/deployments` folder, or try **Helm charts** in `k8s/helm-chart` to package and deploy your application more efficiently. 141 | 142 | --- 143 | 144 | ## Learn More 145 | 146 | To supplement what we’ve done here, I’ve created a **series of blog posts** that walk you through each concept at a deeper level—from explaining Docker’s layered architecture to Kubernetes best practices in production. Check it out here: 147 | 148 | [Our Tech Blogs - Up And Running With Docker & Kubernetes](https://www.getdevops.services/docker/) 149 | 150 | These blogs serve as a companion guide, providing extended explanations, troubleshooting tips, and real-world usage patterns. 151 | 152 | --- 153 | 154 | ## Blog Series (Deep Dive) 155 | 156 | For a comprehensive, step-by-step explanation of all the concepts covered in this repository, and the masterclass, visit our blog: 157 | 158 | [**The DevOps Blog**](https://tech.hindizubaan.com/learn) 159 | 160 | The blog series will cover: 161 | 162 | 1. **[Docker 101: Why Containers Matter - Live](https://tech.hindizubaan.com/learn/docker-101-why-containers-matter)** 163 | 2. **[Introduction to DevOps: Culture, Practices, and Tools](https://tech.hindizubaan.com/learn/introduction-to-devops)** 164 | 2. **Building Your First Docker Image (Dockerfile Deep Dive)** - Yet to be published 165 | 3. **Local Development with Docker Compose** - Yet to be published 166 | 4. **[Introduction to Kubernetes: Concepts and Architecture](https://tech.hindizubaan.com/learn/getting-started-with-kubernetes)** 167 | 5. **Advanced Kubernetes: Ingress, ConfigMaps, Secrets, and Scaling** 168 | 7. **Microservices Architecture and Best Practices** 169 | 8. ...More to follow 170 | 171 | Not all these blogs are yet live, but stay tuned as we will target to release atleast one blog every Wednesday. 172 | 173 | ## Contributing 174 | 175 | Contributions are welcome! Feel free to: 176 | 177 | - Open issues for suggestions and bug reports 178 | - Submit pull requests to improve documentation or add features 179 | 180 | Your feedback helps make this repository a better learning resource. 181 | 182 | --- 183 | 184 | ## License 185 | 186 | This project is licensed under the MIT License. 187 | 188 | Feel free to use and modify this code as you see fit, and don’t forget to share your learnings with the community! 189 | 190 | --- 191 | 192 | Happy containerizing, and see you in the next masterclass! 193 | --------------------------------------------------------------------------------