Modern Creator
JavaScript Mastery · YouTube

DevOps from Zero to Hero: Build and Deploy a Production API

A 5-hour end-to-end crash course that takes you from zero DevOps knowledge to a containerized, CI/CD-deployed Node.js API running on GitHub Actions and Docker Hub.

Posted
8 months ago
Duration
Format
Tutorial
educational
Views
1.1M
20.5K likes
Big Idea

The argument in one line.

DevOps is not a job title or a toolset — it's the discipline of automating the gap between writing code and running it in production, and the fastest way to learn it is to close that gap on a real project while you build it.

Who This Is For

Read if. Skip if.

READ IF YOU ARE…
  • A frontend or full-stack developer who ships code manually and has never set up a CI/CD pipeline.
  • A junior developer preparing for job interviews where Docker and GitHub Actions are listed as requirements.
  • A self-taught engineer who understands Node.js but freezes when the conversation moves to containers, orchestration, or infrastructure.
  • Anyone who has watched a 10-minute Docker demo and still doesn't know how to wire it into a real production workflow.
SKIP IF…
  • You already have CI/CD pipelines, Dockerized deployments, and Kubernetes clusters in production — this is a foundations course, not a senior-level architecture deep-dive.
  • You're looking for a cloud-provider-specific course (AWS, GCP, Azure) — the course sticks to provider-agnostic tooling with cloud deployment left for a follow-up.
  • You want a backend API course — the API itself is the vehicle for DevOps lessons, not the focus.
TL;DR

The full version, fast.

DevOps gets sold as scary gatekeeping, but the core is simple: automate everything between your commit and a live server. This course builds that muscle in order — Git workflows, GitHub Actions pipelines, Docker containerization, Kubernetes orchestration, and Infrastructure as Code — then immediately applies each concept while building Acquisitions, a Node.js API for buying and selling SaaS businesses. By the end you have a real codebase with linting, testing, Dockerization, Arcjet security middleware, and a full CI/CD pipeline that builds and pushes a Docker image to Docker Hub on every push. The theory-to-practice loop is the course's structural advantage over isolated tool tutorials.

Free for members

Chat with this breakdown — free.

Sign in and you get 23 free chat messages on us — ask for the hook, quote a framework, find the exact transcript moment, generate a markdown action plan. Bring your own key when you want unlimited.

Create a free account →
Chapters

Where the time goes.

00:0006:04

01 · Introduction

Cold open dismisses the intimidation around DevOps, frames the gap between 10-minute demos and 6-hour lectures, and previews the full course structure — theory crash courses followed by building Acquisitions, a real Node.js API.

06:0409:45

02 · Introduction to DevOps

Reframes DevOps as a culture and practice rather than a toolset — the collaboration glue between developers and operations that eliminates the 'it works on my machine' failure mode.

09:4520:41

03 · What is DevOps

Covers the Dev/Ops infinity loop, the eight stages of a DevOps lifecycle (plan, code, build, test, release, deploy, operate, monitor), and why companies moved from siloed teams to DevOps culture.

20:4125:42

04 · What You Need to Learn in DevOps

Maps the DevOps learning roadmap: version control, CI/CD, Docker, Kubernetes, IaC, monitoring — and explains the order in which they build on each other.

25:4250:33

05 · Version Control (Git & GitHub)

Git fundamentals through branching strategies, pull requests, and collaborative workflows. Covers feature branching, code review culture, and how Git integrates with every downstream DevOps tool.

50:3355:06

06 · CI/CD Pipelines

Explains the four stages of any CI/CD pipeline (build, test, deploy, monitor) and how GitHub Actions implements them via YAML workflow files triggered on push events.

55:061:03:21

07 · Build Your First Pipeline

Hands-on GitHub Actions workflow: a Node.js test pipeline that runs on ubuntu-latest, installs dependencies, and executes tests automatically on every commit.

1:03:211:55:39

08 · Docker

Docker from first principles: the 'it works on my machine' problem, images vs containers, Dockerfiles, Docker Compose for multi-service setups, volumes, networks, and Docker Desktop hands-on. Includes the lunchbox analogy for containers.

1:55:392:03:35

09 · Kubernetes

Kubernetes concepts: why Docker alone fails at scale, pods vs containers, deployments, services, and the cluster architecture. Introduces minikube for local K8s practice.

2:03:352:26:34

10 · Kubernetes Demo

Live minikube demo: kubectl commands, pod inspection, scaling deployments, and observing self-healing behavior when pods are deleted.

2:26:342:29:37

11 · Infrastructure as Code (IaC)

IaC concepts: Terraform and AWS CloudFormation as examples. Explains why declarative infrastructure is safer than manual console configuration.

2:29:372:30:49

12 · More on DevOps

Transition bridge from theory to the project build — frames Acquisitions as the integration vehicle for every concept covered.

2:30:492:41:44

13 · Setup

Project scaffolding: Node.js + Express, folder structure, environment variables with dotenv, Zod for request validation, and initial repo setup on GitHub.

2:41:442:46:09

14 · ESLint & Prettier

Code quality tooling: ESLint configuration for Node.js, Prettier for formatting, and pre-commit enforcement via lint scripts — the baseline for any team codebase.

2:46:092:54:18

15 · Database Setup

NeonDB serverless Postgres provisioning, Drizzle ORM schema definition, migration workflow, and initial table creation for the Acquisitions domain (users, businesses, deals).

2:54:183:05:15

16 · Logger & Middleware

Winston structured logging setup with log levels and JSON output, Express middleware stack (request logging, error handling), and the correlation between logging discipline and production debuggability.

3:05:153:35:56

17 · Authentication I

JWT-based authentication: register and login routes, bcrypt password hashing, token signing and verification, and RBAC middleware that gates admin vs user routes.

3:35:563:47:53

18 · Warp Setup

Sponsored segment: Warp AI terminal demo showing natural-language command execution, inline code editing, and multi-agent workflows — used to commit auth implementation.

3:47:534:03:11

19 · Security Middleware

Arcjet integration: bot detection, rate limiting per route, email validation on signup, and shield middleware — applied directly to the Acquisitions API routes.

4:03:114:21:49

20 · Dockerization

Writing a production Dockerfile, multi-stage build for smaller images, docker-compose.yml for local dev with hot reload, environment variable injection, and pushing the image to Docker Hub.

4:21:494:38:50

21 · User CRUD

Implementing user management routes (list, get by ID, update, delete) with role-based access control — admins can manage all users, users can only edit their own accounts.

4:38:504:48:35

22 · Testing

Unit and integration testing setup, test runner configuration, and writing tests against the authentication and user endpoints — wired into the CI pipeline so tests run on every push.

4:48:355:00:07

23 · Next Steps

CICD pipeline finalization: GitHub Actions workflows for Docker build-and-push, adding Docker Hub credentials as repository secrets, and a pointer to the upcoming Backend Pro course for cloud deployment, self-hosted Postgres, and advanced pipelines.

Atomic Insights

Lines worth screenshotting.

  • DevOps is not a job title — it's the practice of closing the gap between writing code and running it reliably in production.
  • Docker was created because 'it works on my machine' is a deployment strategy that fails at the worst possible moment.
  • A CI/CD pipeline is just a checklist that runs automatically — lint, test, build, deploy — every time you push a commit.
  • Kubernetes doesn't replace Docker; it orchestrates Docker containers the way a conductor manages an orchestra: same instruments, coordinated execution.
  • Infrastructure as Code means your server configuration is version-controlled, reviewable, and reproducible — the same way your application code is.
  • JWT authentication and role-based access control are table stakes for any production API; wiring them in from the start is cheaper than retrofitting them.
  • Structured logging with Winston means every request leaves a breadcrumb trail — when something breaks in production, you have evidence, not guesswork.
  • Security middleware like Arcjet protects against bots, spam, and rate-limit abuse without requiring you to build those defenses from scratch.
  • A GitHub Actions workflow is just a YAML file in your repo — there's no separate service to configure, no dashboard to log into, no vendor lock-in beyond the syntax.
  • The most valuable DevOps habit is not a tool — it's committing, pushing, and verifying pipeline output on every meaningful change rather than batching them.
  • Containerizing an app for both dev and production environments with the same Dockerfile eliminates the 'it deploys fine locally' class of bug entirely.
  • Health-check endpoints are not optional polish — they are the signal your orchestration layer uses to decide whether your container is fit to receive traffic.
  • Drizzle ORM with NeonDB gives you type-safe database queries and a serverless Postgres backend that can cold-start without you managing a connection pool.
  • The single biggest bottleneck in a developer's production readiness is not knowing what happens between `git push` and a live URL — this course maps every step.
Takeaway

DevOps only clicks when you build through it.

WHAT TO LEARN

Every DevOps concept — containers, pipelines, orchestration — is abstract until you wire it into a real codebase; this course's structure forces that connection.

01Introduction
  • The two failure modes of DevOps education are the 10-minute 'it works' demo and the 6-hour theory lecture — both skip the part where you connect the concepts to a real codebase.
  • Knowing what a pipeline contains before you build one (Git → CI/CD → Docker → K8s → IaC → monitoring) lets you understand each tool's role rather than learning tools in isolation.
02Introduction to DevOps
  • DevOps emerged when developers and operations teams realized that siloed handoffs created slower releases, more blame, and more outages — the culture shift came before the toolset.
  • The infinity loop model (plan → code → build → test → release → deploy → operate → monitor) is the right mental model because it's a cycle, not a one-way handoff.
03What is DevOps
  • The eight stages of the DevOps lifecycle map directly to the tools you'll learn — skipping one stage means a gap in your automation and a manual step somewhere in your workflow.
  • DevOps culture values shared ownership of failures — when the pipeline breaks, it's a team problem, not a handoff blame game.
04What You Need to Learn in DevOps
  • The DevOps learning order matters: Git before pipelines, Docker before Kubernetes, local infrastructure before cloud — each layer depends on the one before it.
  • Learning tools out of order (starting with Kubernetes before Docker) is the most common reason developers feel lost in DevOps tutorials.
05Version Control (Git & GitHub)
  • Feature branching keeps main always deployable — every broken experiment lives on its own branch and only merges when it passes review and tests.
  • Pull requests are not just a code review tool — they are the checkpoint where CI runs automatically, so the review and the pipeline result arrive together.
  • Your Git history is your project's audit trail — meaningful commit messages are not a style preference, they're how you reconstruct what changed when something breaks in production.
06CI/CD Pipelines
  • Every CI/CD pipeline is a four-stage machine: build (does the code compile?), test (does it behave correctly?), deploy (push the artifact), monitor (did the deploy succeed?) — complexity is variation on this.
  • The pipeline runs on a clean environment every time, which makes 'it passed locally' irrelevant — if it doesn't pass in CI, it doesn't ship.
07Build Your First Pipeline
  • A GitHub Actions workflow file is three things: a trigger (on push), a runner (ubuntu-latest), and a list of steps — the simplest working pipeline fits in 15 lines of YAML.
  • Running tests automatically on every push costs nothing except the time to write the tests — skipping this step costs debugging time on every future push.
08Docker
  • A Docker image is a snapshot of your application plus every dependency it needs — the container is the running instance of that snapshot, isolated from the host.
  • Docker Compose is how you wire multiple containers together locally (API + database + cache) with a single `docker-compose up` command.
  • Every Dockerfile should be treated like application code — reviewed, versioned, and tested — because it determines exactly what runs in production.
  • Volumes let containers persist data between restarts and share files with the host; without them, every container restart wipes your database.
09Kubernetes
  • Kubernetes solves three problems Docker alone can't: automatic restarts when containers crash, load balancing across multiple instances, and rolling deployments with zero downtime.
  • A Pod is the smallest deployable unit in Kubernetes — usually one container, sometimes a sidecar — and a Deployment manages a set of identical Pods.
10Kubernetes Demo
  • Deleting a pod in Kubernetes and watching it instantly respawn is the most visceral way to understand why orchestration matters — the cluster enforces your declared state.
  • kubectl is the command-line interface to Kubernetes — `apply`, `get`, `describe`, `logs`, and `delete` cover 90% of day-to-day cluster operations.
11Infrastructure as Code (IaC)
  • IaC means your infrastructure lives in a git repo alongside your application code — you can roll back a bad infrastructure change the same way you roll back a bad code change.
  • Terraform and CloudFormation are declarative: you describe the end state, and the tool figures out what to create, update, or destroy to get there.
13Setup
  • Zod for request validation at the API boundary means invalid data never reaches your business logic — errors are caught and formatted before they cause unexpected crashes.
  • Environment variables managed via dotenv and validated at startup prevent the class of bugs where a missing config key causes a silent failure hours later in production.
14ESLint & Prettier
  • ESLint and Prettier are not optional polish — they are the automated reviewers that catch entire categories of bugs and inconsistencies before a human ever reads the code.
  • Enforcing formatting in CI means style debates never happen in code review — the pipeline fails, the author fixes it, and reviewers focus on logic.
15Database Setup
  • Drizzle ORM schema files are your database documentation — they describe every table, column, and relationship in TypeScript, which your IDE and CI can both read and validate.
  • NeonDB's branching feature lets you create a copy of your production database for each feature branch, so you can test schema changes without touching production data.
16Logger & Middleware
  • Structured JSON logging means every log entry is machine-readable — you can filter, aggregate, and alert on logs programmatically rather than grepping through plain text.
  • Global error-handling middleware is the last line of defense before an unhandled exception crashes your Node process — it catches what your route handlers miss and returns a consistent error shape.
17Authentication I
  • Never store passwords — store bcrypt hashes with a cost factor high enough to make brute-force attacks expensive, and never roll your own hashing.
  • JWT verification middleware that runs before route handlers means protected routes never execute with an invalid or expired token — the access control is enforced in one place, not scattered across handlers.
  • RBAC middleware that checks a `role` field on the JWT payload keeps permission logic out of individual route handlers and makes it easy to add new roles without touching existing routes.
19Security Middleware
  • Rate limiting per route is more precise than global rate limiting — an auth endpoint needs a much stricter limit than a public read endpoint.
  • Bot detection at the middleware layer stops abuse before it hits your database or business logic — no amount of business-layer validation defends against a flood of automated requests.
  • Email validation on signup (Arcjet's disposable email detection) reduces the volume of low-quality accounts that inflate your user count and dilute engagement metrics.
20Dockerization
  • Multi-stage Docker builds produce smaller production images by separating the build environment (with dev tools) from the runtime image (just the compiled output and production dependencies).
  • Docker Compose for local development with volume mounts and hot reload means your dev environment is containerized but still fast — changes in your editor reflect immediately without a full image rebuild.
  • Pushing a tagged image to Docker Hub in CI creates a versioned artifact that any environment (staging, production, a teammate's machine) can pull and run without a build step.
21User CRUD
  • Admin-only routes that enforce RBAC at the middleware level are safer than checking roles inside the handler — a handler bug can't accidentally skip the role check if the middleware never calls it.
  • Separating user self-service operations (update own profile) from admin operations (manage all users) in distinct route groups makes permission auditing straightforward.
22Testing
  • Integration tests that hit your actual routes (with a test database) catch the class of bugs that unit tests miss — the ones that only appear when middleware, handlers, and the database interact.
  • Wiring tests into the CI pipeline changes testing from a discipline into a constraint — the pipeline won't pass unless the tests do, so every merge is a tested merge.
23Next Steps
  • GitHub Actions secrets for Docker credentials mean your Docker Hub username and password never appear in your YAML files or commit history — credential hygiene is non-negotiable in public repos.
  • A complete CI/CD pipeline for a containerized API — lint, test, Docker build, push to registry — is the professional minimum; everything beyond it (K8s deployment, canary releases, notifications) builds on this foundation.
Glossary

Terms worth knowing.

CI/CD
Continuous Integration / Continuous Deployment — a pipeline that automatically runs tests, builds, and deploys your code every time you push a change, replacing manual release steps.
Docker
A containerization platform that packages an application and all its dependencies into a self-contained unit (a container) that runs identically on any machine.
Dockerfile
A text recipe that tells Docker how to build a container image for your application — from base OS through installed packages to the startup command.
Docker Hub
A cloud registry for storing and distributing Docker images, similar to npm for packages or GitHub for code.
Kubernetes (K8s)
An orchestration platform that manages clusters of Docker containers — handling scaling, self-healing, rolling deployments, and load balancing automatically.
Infrastructure as Code (IaC)
The practice of defining server infrastructure (networks, VMs, storage) in version-controlled configuration files rather than clicking through cloud dashboards.
GitHub Actions
GitHub's built-in CI/CD system — YAML workflow files inside your repo that run on push, PR, or schedule events using GitHub's cloud runners.
JWT (JSON Web Token)
A compact, signed token that proves who a user is without requiring a database lookup on every request — the standard mechanism for stateless API authentication.
RBAC (Role-Based Access Control)
A permission model where access is tied to a role (admin, user) rather than to individual accounts, making permission changes a matter of updating a role definition.
Arcjet
A developer-focused security middleware layer that adds bot protection, rate limiting, and email validation to an API with minimal configuration.
NeonDB
A serverless Postgres provider with branching and cold-start capabilities — a hosted Postgres database that scales to zero when idle.
Drizzle ORM
A TypeScript-first ORM for SQL databases that generates fully type-safe queries with minimal runtime overhead and no code generation step.
Winston
A structured logging library for Node.js that formats log output as JSON or plain text, with configurable log levels and transports (console, file, cloud).
minikube
A tool that runs a single-node Kubernetes cluster locally inside a VM or Docker container — used for learning and testing Kubernetes without a cloud provider.
Resources

Things they pointed at.

Quotables

Lines you could clip.

00:26
DevOps gets sold as a scary gatekeeping monster. And the problem is that no one shows you how this puzzle fits together.
Calls out the core frustration of the target audience in one sentenceTikTok hook↗ Tweet quote
1:04:24
It works on my machine. Have you ever heard or said this? This is why Docker was created in 2013.
Universal developer recognition moment — relatable opener for any Docker shortIG reel cold open↗ Tweet quote
83:15:00
For an even deeper dive, including self-hosting Postgres, learning cloud providers like AWS, deploying dockerized applications, building advanced pipelines, setting up notifications, and more, the ultimate back end course is exactly what you need.
Course teaser — clean CTA pull quotenewsletter pull-quote↗ Tweet quote
The Script

Word for word.

Read-along

Don't just watch it. Burn it in.

See every word as it's spoken — crank it to 2× and still catch all of it. The same dual-channel trick behind Amazon's Kindle + Audible.

00:00DevOps. The word that makes half of YouTube tutorials feel like you don't really belong. Like, you should stick to HTML while the real engineers handle the big stuff.
00:10But when you try learning it yourself, you're slammed with Docker commands that look like alien code, YAML files that read like broken poetry, and the jungle of tools that never seem to connect. That's the trap.
00:23DevOps gets sold as a scary gatekeeping monster. And the problem is that no one shows you how this puzzle fits together.
00:31You either get a ten minute, look, it works demo or a six hour death by PowerPoint lecture that has nothing to do with the apps you actually wanna ship. So welcome to the full DevOps course where you'll finally see the whole picture step by step as we go from fundamentals to production ready API that looks and feels like something you'd actually run-in the real world.
00:57Here's what you'll get. A no b s intro to what DevOps actually is and why it matters. A hands on Git crash course so version control stops feeling like black magic.
01:07CICD pipelines that deploy your code automatically. A Docker crash course to containerize your apps for dev and production, Kubernetes deployments like real companies use them, infrastructure as code to spin up environments on demand, and monitoring, logging, and security baked in from the start.
01:26And, of course, we won't end there. In the signature JavaScript mastery style, you'll then use this learned knowledge to build and deploy acquisitions, a real world API for buying and selling SaaS businesses with tons of features like JWT based authentication and authorization, role based access control for admins and users, user management for accounts, business listings to create, update, delete, and browse, deal management to track deals from pending to completed, health monitoring of all the endpoints, request validation using Zod, structured logging with Winston, completely secured endpoints using ArcGit, a painless security system for developers that protects your app from bots, spam, and abuse in real time.
02:14For the database, we're using Postgres with NeonDB. It's serverless, lightning fast, and built for modern cloud apps that even work locally. Adding onto it, Driscoll RM for type safe database queries, Docker for containerizing applications across dev and production environments, Kubernetes to orchestrate containers at scale, Jest and Supertest for automated testing and validating application behavior, CICD pipelines for linting, testing, deployment, and automation, clean absolute imports, ESLint and Prettier for clean, maintainable code, and much more using WARP, the fastest way to build and ship applications with AI.
02:56It's an all in one environment where you run commands, write code, and prompt top AI agents in parallel.
03:03This isn't just another DevOps demo. By the end, you'll have hands on experience and production ready back end you can actually ship tomorrow. And if you wanna dive deeper into building scalable, production ready back end systems from the ground up in a beginner friendly way, check out the upcoming ultimate back end developer course.
03:23It's the perfect combination of strong foundations taking you all the way from core networking concepts all the way to building and deploying APIs with full DevOps and AWS integration.
03:35You can either join the waitlist if it's not out, or if you're lucky, it's already out and you can get started with it right away. So grab your cup of coffee and let's ship code like 6 figure salary engineers. By the way, I've recently opened up channel memberships.
03:50So if you've been enjoying these videos and wanna support the channel, that's one of the best ways to do it. In return, you'll also get access to extra resources like the Figma design files from my tutorials, detailed cheat sheets paired with this video, and even complete ebooks. Plus, as a member, you'll actually get a say in what I record next for YouTube.
04:09It's totally optional, but if you've been finding value here and you're not quite ready to become pro yet, if you wanna dive a bit deeper while helping me make these full courses free, I would be thankful if you joined today. Before we start building, let's get our environment set up. Because here's the thing, DevOps isn't something you watch.
04:27It's something you do. And to actually follow along without getting stuck, you'll need the same stack I'm using. The good news, it's all free.
04:35And these are real tools companies are using every day. First, make sure you've got Node. Js installed.
04:42That's the core runtime we'll be using to build our back end API. Then our database. We'll be using a cloud Postgres provider called NeonDB.
04:51This will power up everything in our API, so you'll want an account ready before we dive in. Click the link down in the description, create an account, and you're good to go. Next, ArcGit.
05:01You can build the cleanest API in the world, but if it's wide open to bots, spam, or abuse, it's useless. ArcGit gives you a real time protection while you build, so security isn't something you tackle on later.
05:15It's there from day one. And finally, warp.
05:18This is where all the action happens. Running commands, shipping code, and even prompting AI agents to speed up your workflow. It's a modern AI powered developer environment you'll never wanna leave.
05:30And right now, their pro plan is literally $1, which makes it kind of a no brainer.
05:37So once you've set those up, you'll have the exact stack I'm using. That way, when I say run this command, you can run it too with no interruptions, no deters, just straight into building.
05:49Very soon, you'll build and deploy your very own production ready API. But first, let's dive right into the crash course.
06:05You've heard this term a 100 times, but let's be real. DevOps sounds scary as hell.
06:12Right? Is it a job? A tool?
06:15A secret club for 10x engineers with six monitors and a tolerance for drinking five Red Bulls. You're not alone. Everyone feels like this at first.
06:26DevOps sounds massive, mysterious, and way too complicated.
06:32But here's the truth. DevOps is simpler than it sounds. And in this crash course, I'll break it down like we're just chatting over coffee.
06:41Here's how software used to work. You write some code.
06:46It runs on your laptop, you deploy to a server, done. And if you're a Vibe coder, you might even skip testing entirely, push it straight to production, and call it a day.
06:58That actually works if your app is tiny and no one cares if it crashes. But the second your app grows, traffic spikes, and the real money is on the line, that's when this vibe ship starts sinking.
07:13Servers crash, hackers attack, thousands of users log in at once, and your app dies at 3AM.
07:21Tell me, who handles all that chaos? Early on, it was the same developers writing features, but most devs aren't trained to babysit servers or fight off security attacks.
07:35Their job is to build, not panic at midnight. So companies created operation teams or ops.
07:44They managed servers, scaled apps, monitored uptime and performance, applied security patches, and got woken up at 3AM when everything broke.
07:54Honestly, props to them. Ops became the guardians of stability while devs focused on speed. So now you've got two camps.
08:04Devs who ship features fast and ops who keep things stable. So what happened? Well, a tug of war.
08:13Devs tossed code over the wall. Ops slowed them down, blocked releases, or spent weeks cleaning up.
08:22Result, frustration, silos, and slow delivery.
08:27So finally, DevOps was the solution. Not a tool or a role, a culture shift.
08:33It's about breaking down silos so devs and ops work together. Backed by automation, they deliver software faster and safer.
08:43DevOps is a culture of collaboration, a set of practices for building, testing, and releasing reliably, and an automation toolbox for deployments, monitoring, and infrastructure.
08:56Think of your app like a restaurant. Devs are the chefs who are cooking meals or code, and ops are waiters and managers getting meals to customers.
09:07They handle servers and uptime. Without DevOps, chefs would toss random dishes onto the counter and hope for the best. Chaos.
09:16With DevOps, the kitchen runs like a well oiled machine. Orders flow smoothly, food is consistent, and customers are happy.
09:25So why not just vibe code? I mean, you can skip all of this if your project is just for fun. But once you have real users, especially paying users, Vibe coding quickly turns into Vibe burning.
09:40So let's skip the burnout and learn DevOps the right way.
09:47You have a sense of DevOps, but not the full picture yet. You might be thinking, I get that DevOps is a culture, but how do I learn it, and what does it look like day to day?
09:59If you've ever googled or chat GPT'd, if that's even a word, the term DevOps, you've definitely come across the famous infinity loop diagram. You know, the one where arrows chase each other endlessly, labeled plan, code, build, test, release, deploy, operate, and monitor.
10:19That loop is not just a conference graphic, even though it looks that way. It is the process of how an idea becomes software in users' hands and then gets better with feedback.
10:31Dev and ops are connected the entire time. So let me show you what each stage means in practice. We start with a plan.
10:41Imagine you're building a multimillion dollar SaaS application. Doesn't hurt to imagine. Right?
10:46Before a single line of code, you decide what you wanna build, when to ship, who owns that, and how you will measure success. Page speed, user growth, revenue.
10:58And make it traceable, not sticky notes that'll disappear. Use tools that the team will actually keep up to date. Be it Jira, Linear, GitHub projects, or just Notion.
11:09The tool is less important than the discipline. A plan without action is just a dream, so make it explicit and visible.
11:18Once we're done with planning, we move over to code. Code quality matters as much as code output, So write clean, modular, and testable code that others can extend.
11:30Use git with reviews, branch rules or automated checks, ship readable code, and not hacky solutions that only you can understand. And now we enter the build phase.
11:42Because when you finish writing source code, it's still just raw text files. Those files cannot always be executed directly in production. They often need to get compiled or transpiled.
11:55Dependencies need to get installed. It needs to get bundled or packaged like in Docker, and we need to check for linting or security mistakes. The build step is all about preparing code so it can actually run.
12:09An artifact, a ready to run package. Think of it like baking dough into bread.
12:15You can't eat raw dough, which is the source code, so you can bake it, build, into something edible, an artifact. In DevOps, this process is automated for consistency.
12:27You build Docker images, run containers through make files, and build them every time you make a commit. You'll learn all of that in the next lesson.
12:36And then we get to testing. Of course, you wouldn't want untested code to reach production. The test stage exists to catch problems early when they're still cheap and easy to fix.
12:49Automated tests run as part of the CI pipeline covering everything from basic unit tests to integration tests, end to end workflows, and security scans. For instance, let's say you're building a payments feature.
13:04Unit tests verify the math for totals, integration tests ensure the checkout flow works with a database, and end to end tests simulate a customer actually completing a purchase. Security tests scan for vulnerable dependencies or insecure patterns, And by the time the code passes all of these stages, the team can be confident that it behaves as expected and won't break the system when deployed.
13:30All of this runs in automation pipelines. So when all the tests pass, the build is finally marked as ready for release.
13:39This doesn't mean it's already running in production. It means that the artifact has been approved and queued for deployment. Think of this as moving from dev to ops.
13:50In practice, the release stage involves versioning, tagging artifacts, and pushing them into a release repository like Docker Hub, Nexus, or an internal store.
14:01This ensures the exact same build that was tested will be the one deployed later on. No surprises or mismatches. And then comes the big moment, deployment.
14:12In traditional setups, someone might have to log into a server at 2AM to run some manual scripts. But in DevOps, deployments are automated and repeatable. Pipelines handle the process, and tools like Kubernetes orchestrate deployments at scale.
14:28But more on that soon. For now, understand that you have to use these tools to build pipelines for automated deployments that scale up and down based on defined needs.
14:40For example, when deploying a new version of a web app, Kubernetes might spin up new pods with the updated code while gradually phasing out the old ones, a strategy known as rolling deployment. This ensures a minimal downtime and a smooth user experience.
14:57Some teams even practice blue green deployments or canary releases to test new versions with a small subset of users before rolling out widely. And once deployed, the application is live, but the work isn't over. You have to operate.
15:14See, the operate stage is all about ensuring the system continues to run reliably under real world conditions. This includes monitoring server health, scaling resources when traffic spikes, applying security patches, and managing infrastructure configurations.
15:32Think about an ecommerce platform on a Black Friday. At 2PM, traffic might skyrocket requiring the system to scale horizontally across multiple servers.
15:43At 2AM, when traffic is low, resources can be scaled down to save costs.
15:50DevOps teams ensure that the system is resilient, stable, and performant at all times. So finally, we have the monitoring.
15:59Here, teams gather data about the system's performance, uptime, error rates, and business metrics. Monitoring tools like Prometheus, Grafana, Datadog, New Relic, or Sentry act like CCTV cameras for your app.
16:15They let you see not only technical metrics like CPU usage, latency, or error logs, but also business outcomes like orders processed, sign ups completed, and revenue generated.
16:28For instance, if your new checkout flow increases cart abandonment, monitoring will catch it.
16:34That insight then loops back into the plan stage where the team can adjust priorities and improve the product, and then the cycle starts again. That's the beauty of DevOps. It never really stops.
16:47Each stage feeds the next, forming a continuous loop of building, testing, delivering, operating, and improving.
16:56And it's not just a set of tools or a job title. It's a culture of constant learning and iteration. But wait, does that mean that as a DevOps engineer, you're expected to handle everything in this cycle.
17:09Are you supposed to code like a developer, test like a QA, deploy like ops, and monitor like SREs?
17:17That's a great question. No. You're not supposed to do everything.
17:21This is one of the biggest misconceptions about DevOps. If DevOps is a culture, a way of working where dev, ops, QA, and security work closely together, then a DevOps engineer is not a superhuman who replaces all those roles.
17:38Instead, your role is all about bridging the gap between teams, automating the boring or manual work so teams can move faster, and setting up tools and practices that make collaboration smoother.
17:52See, developers still write features. QA still ensures quality.
17:58Ops still keep server running. But as a DevOps engineer, you make sure that all these moving parts are connected and running without chaos.
18:08So what does a DevOps engineer actually do? You're not here to replace developers, QA, or ops.
18:15You're here to connect all the dots and keep things running smoothly. So let's run through the entire life cycle once again, but from your point of view. When it comes to the planning phase, developers and product managers decide what to build.
18:32You make sure that planning tools like Jira, Confluence, or Notion are wired into your pipeline. For example, when someone closes a Jira ticket, it should link straight to a commit or a deployment log.
18:45In the coding phase, you're not writing every feature, but you set the rules of the road. Branching strategies, code review requirements, auto linting, and security scans all run automatically, so code quality stays high.
19:00Then in the build phase is where raw code becomes something that can actually run anywhere, like a Docker image. Your job is to set up pipelines so this happens on every commit consistently with zero manual steps.
19:15When it comes to testing, QA owns it, but you make it effortless. You integrate unit tests, integration tests, and security scans into the pipeline so bugs get caught early before they even reach production.
19:30And then for the release and deploy phases, this is where you shine. Instead of someone SSH ing into a server at midnight, you automate deployments with Kubernetes, Terraform, or Helm.
19:41In the operation phase, the ops teams keep everything alive, but you help them codify the infrastructure with Terraform or CloudFormation, set scaling rules, and make systems highly available.
19:54An example of this would be spinning up a new AWS cluster with a single config file instead of spending hours in the console. And finally, in the monitor and feedback phase, once the code is live, you keep watch.
20:08Metrics, logs, dashboards, and alerts feed straight to Slack or Teams.
20:14If something breaks or slows down, you know it before customers do. So, no, you're not coding the app and writing tests and running servers all by yourself. You're the air traffic controller.
20:28Developers fly the planes or build the features. Ops is the ground crew. QA is the safety check, and you're in the tower keeping everything smooth and safe.
20:43Let's dive into the heartbeat of DevOps, CICD. But what does that actually mean?
20:49Well, picture a kitchen in a restaurant. The chef chops the vegetables, the assistant cooks them, the manager inspects the dish, and the waiter finally serves it to the table.
21:01If every step was manual, the service would be slow. In the same way, we manually go through different stages, such as coding, building, testing, and deployment.
21:13Pretty manual. Right? Now imagine a conveyor belt moving the dish automatically from one step to the other.
21:20That's a pipeline. It's an automated conveyor belt for software. So let's break it down.
21:26CI stands for continuous integration. Every time a developer pushes code, tests run automatically.
21:34And if something breaks, the pipeline stops, and you fix it before moving forward. CD stands for continuous deployment where once tests pass, the app is automatically deployed to staging or production.
21:48No late night manual deployments. Your job is to write these pipelines using tools like GitHub actions, GitLab CI, Jenkins, or CircleCI.
21:59You'll define each step from build, test, release, to deploy. Back in the day, running apps was messy. You'd install them directly on servers, and if one needed version 18 of Node JS and another one needed version 20, you'd have conflicts everywhere.
22:18Containers solve this by packaging apps with everything they need. The most popular tool here is Docker.
22:25Now imagine not just one container, but hundreds of them for microservices, background jobs, and databases.
22:33Someone needs to decide where do these containers run, how many copies do we need right now, what happens if one crashes, and how do they securely talk to each other.
22:45That's called orchestration, and the go to tool here is Kubernetes, which acts like the conductor of an orchestra, coordinating containers so everything runs smoothly and scales automatically.
22:57Traditionally, people clicked around in a cloud dashboard to create servers and networks. That's like building IKEA furniture without instructions. Slow, error prone, and almost impossible to recreate.
23:11With infrastructure as code or ISC for short, you describe your entire setup. Servers, networks, databases, all in code.
23:20You store it in Git, review changes, and recreate environments anytime. Tools like Terraform or AWS CloudFormation make your infrastructure predictable and repeatable.
23:33So if something goes down, you just rerun your code to rebuild it from scratch. And almost no company runs their own physical service anymore. Instead, they run them from cloud providers like AWS, which are Amazon's web services, Azure from Microsoft, or GCP, which is Google Cloud Platform.
23:53You don't need to master all three. Focus on one primary provider. AWS is the most common.
24:00And get comfortable deploying apps, setting up networks, and managing storage. Once you know one well, switching to another is easier, like learning to drive one car and then trying out a different brand.
24:13And once your app is live, you need visibility. Monitoring and logging tools show what's happening in real time, not just server performance, but also user behavior and errors.
24:25You've seen me use Sentry in some of my projects to track errors and performance. That's a great starting point. Some DevOps teams use different tools like Prometheus and Grafana for custom metrics and dashboards, ELK Stack or Datadog for centralized logs, or Posthoc for product analytics and funnels.
24:44With the right setup, you know about problems before your users do. And finally, scripting ties everything together.
24:51A DevOps engineer should be comfortable with at least bash or shell scripting and one programming language like Python or JavaScript. Why? Because automation is the heart of DevOps.
25:04And if you're onboarding 10 new developers and need to set up their accounts and permissions, don't just click through dashboards 50 times. Write a script once and automate the whole process.
25:15I know. It's a lot. But if you take it step by step from Git, pipeline, containers, ISC, cloud, monitoring and scripting, you'll go from confused to confident pretty fast.
25:29And in this video, we'll skim the surface of each one of these topics in a beginner friendly way. And if you'd like some deep dives on advanced DevOps concepts, drop a comment down below, and I'll make it happen. Imagine you're working on a coding project, and you make a mistake that breaks everything.
25:47Your boss would most likely fire you if you were even able to get a job in the first place. Without Git, you'd have no easy way to go back and undo the changes. You're toasted.
25:56Git is the industry standard. Most companies, team, and open source projects use Git, so naturally, every job description mentions it. Learning it isn't just a nice to have.
26:08It's your Git good or get out moment. It's a must for any serious developer wanting to land the job. So what is Git, and why is it so popular?
26:17Git is a distributed version control system. Sounds fancy.
26:22Right? Well, let's break it down. The version control part helps you track and manage code changes over time, while distributed means that every developer's computer has a complete copy of the code base, including its entire history of changes and information about who changed what and when allowing you to git blame someone.
26:42Hopefully, won't blame you. But do you really need it? Can you code without using git?
26:49Well, of course you can. But then your workflow would look something like this. You start coding your project in a folder named my project.
26:58And as you make progress, you worry about losing your work, so you create copies. My project v one, v two, v three, and so on. Then your colleague asks you for the latest version.
27:10You zip up my project v three and email it over. They made some changes and sent it back as my project v three johns changes dot zip. Meanwhile, you've continued to work, so now you have my project v four.
27:23You then need to manually compare John's changes with your v four and create a v five incorporating everyone's work. And then a week later, you realize you accidentally removed a crucial feature in v two.
27:36You dig through old folders trying to figure out what changed between versions. Now imagine doing this with 10 developers, each working on different features.
27:46It's a recipe for chaos, lost work, and countless hours wasted on version management system instead of actual coding. Git solves all of these problems and more. It tracks every change automatically, allows multiple people to work on the same project seamlessly, and lets you easily navigate through your project's history.
28:07No more final version v two, final, really final zip files. Git does all of this for you but in a much more powerful and organized way. To get started, you need Git installed.
28:19Whether on Windows, Mac, or Linux, it's just two clicks away. Google, download Git, and Git it for your operating system.
28:27Once Git is installed, open up your terminal. Nowadays, I prefer using a terminal built into my IDE. First things first, let's check whether you've installed Git properly.
28:36Run git dash dash version, and you'll get back the version that is installed on your device. Next, you need to configure git to work with your name and email.
28:47This is just to track who made the changes in the project so your colleagues know who to blame. Here's the command. Git config dash dash global user dot name, and then in single quotes, put in your name.
28:59Once you do that, you can repeat the same command, but this time, instead of changing user dot name, we'll change user dot email. And here, you can enter your email. Press enter, and that's it.
29:09You're all set up. Now let's talk about repositories. A repo or a repository is where Git tracks everything in your project.
29:18Think of it like a folder that stores all the versions of your code. Simply put, if a folder is being tracked by Git, we call it a repo. Now let's create a new repository.
29:28In your terminal, type git init and press enter.
29:32As you can see, Git has just initialized a new repository. On top of the success message, we can also see a warning. In previous times, the default name of a branch has been master, but nowadays, you'll see main used much more frequently as the name for the primary branch.
29:46So let's immediately fix it by configuring the initial branch name. You can copy this command right here, and at the end, you can just say main. Now considering that we have just changed the initial configuration settings, we have to create a new folder.
30:00Create a new one called something like mastering git. Open it within your editor, and then rerun git init.
30:08As you can see here and here, now we're in the main branch. That means that git has initialized an empty repository.
30:16You won't see any changes yet in your folder, but a hidden dot git folder has been created inside your directory. You don't need to touch this folder. Git handles everything inside from commit history, branches you'll make, remote repos, and more.
30:31Most of the time, Git will already come preinitialized by the framework or library that you use set up your project with. That's how integrated Git is into every developer's life.
30:41So now that we have this main right here, what does that exactly mean? Well, main is the default branch name of your repo created by Git. Every time you initialize Git, this branch will be automatically created for you.
30:55I'll teach you more about Git branches soon, but for now, know that a branch is nothing but a parallel version of your project. Alright. Let's add some files and track changes.
31:05I'll create a new file called hello dot j s. And you can see how smart WebStorm is. It automatically asks me whether I want to add it to git.
31:14But for now, I'll cancel that because I wanna explain everything manually. Let's make it simply run a console dot log that prints hello git. Alongside this file, let's create another new file, and I'll call it read me dot m d.
31:28In here, we can do something similar and say, hello, git. And now run git status.
31:36Git will tell you that you're currently on the main branch, that there are no commits yet, and that there are two untracked files, one of which is a markdown document. So to track it, use git add readme dot m d.
31:52After adding a file, we need to commit it. Committing in git is like taking a snapshot of your project at a certain point. Think of it as creating a whole new copy of your folder and telling git to remember when you did it, at what time.
32:05So in the future, if anything happens, you'll time travel to this folder with the commit name you specified to git and see what you had in there. It's essential to commit your changes regularly. Regular commits help you keep track of your progress and make it easier to revert to previous versions if you break something.
32:23You can commit by running git commit dash m, which stands for message, and then in single quoted strings, you can add that message. For example, add readme dot m d file. There we go.
32:37Congrats. You just created a checkpoint in your project's history. Now let's try running git status again to see what it shows.
32:45As you can notice, that other file, hello dot j s, is still there. It's not tracked. We asked git to track only the readme file.
32:54To track this file or other files that you may create, we'll have to run a similar command. It'd be too much work to commit each file individually. Thankfully, we have a command that commits all the files we've created or modified that git is not tracking yet.
33:09To see this in action, let's create another file, test dot j s, and let's add a simple console log that simply console logs a string of test. Now to track both files and commit them in a single commit action, we can do that by running git add dot. The dot after git add tells git to add all files created, modified, or deleted to the git tracking.
33:33Next, as usual, we can specify the commit name for this tracked version by using git commit dash m add hello and test files.
33:45There we go. So now we can see that all of these files are tracked. And since I'm using WebStorm, it also has a hidden dot idea folder, so it added it to tracking as well, which I'm okay with.
33:56Well done. Now to see the history of all commits we've created, we can use a new command, git log.
34:02And there we have it, our git history. It contains a commit ID or a hash automatically created by git, the author we specified when using git config, a timestamp, and the commit message we provided.
34:16Great. But how do we switch to an older commit and restore it? Let's say the commit add hello and test files introduces some buggy code, and we want to restore our project to a previous version without these files.
34:28Our brain would immediately suggest deleting those files entirely or clearing up their code. And if you do that, you'll most likely break your production because other files depend on those files.
34:39So instead of deleting them manually to restore to the first version where we had only committed the readme file, we can use a new command. First, you have to copy the commit hash. Yours is gonna be different from mine, so make sure to copy yours.
34:53I'll get this one first that says add read me m d file, and I'll press copy. Then you have to exit this git log by pressing the q letter on the keyboard, and then you can use a command git checkout, and then you can provide a hash of a specific commit or a branch you wanna checkout to. Now press enter.
35:11Okay. Something happened. First of all, our two files are gone.
35:17Detached head, experimental changes, what's happening? Well, in git, there is a concept of a head, which refers to the pointer pointing to the latest commit you've created.
35:27When we created our second commit, our head shifted from readme commit to the latest add hello and test files commit. But when we ran git checkout command, we moved the head to the previous older commit.
35:40That's why we got this detached head warning. It's a state where the head pointer no longer points to the latest branch commit, and the rest of this message tells you that you can create a new branch off of this commit.
35:52But don't worry. Your files are still somewhere. When you use a git checkout command, you're simply viewing the repository state as it was at the time of a specific commit.
36:02Like right now, we're viewing a snapshot of your code base at a previous moment in time when we only had a read me dot m d file. The beauty of this is that all the logs and files whether created or modified, remain untouched. The git checkout command won't delete any logs or history, so you can safely explore past states without worry.
36:23But what if you actually wanna discard changes made after dot commit? Maybe you wanna quickly roll back to a stable state after an issue hits production, tidy up messy commits to look more professional, or undo a bad push you regret making.
36:36Perhaps you've been experimenting with a refactor that didn't pan out or you need to recover from a messy merge conflict. Thankfully, Git provides a few commands that'll help you in these scenarios.
36:47And I'll teach you how all of that works very soon. So just keep watching and we'll dive into these more advanced commands that are really gonna help you, well, fix a broken production. Now to go back to our current state, which is often called the head state, you simply have to run git checkout main.
37:05And there we go. Previous head position was at the hash of this checkout, and now you switch the branch to main.
37:11You can see the same thing happen right here on the bottom right or the top left, depending where your branching is. And if you made any changes while in the detached head state and you want to discard them, you can do the same thing, git checkout dash f, that means force, and then get back to main.
37:27In this case, we're git. We're already on main. And that's it.
37:31You already know more about git than most developers do. Of course, we'll dive deeper into advanced use cases and tips and tricks soon, but now let's talk about GitHub and how it differs from Git.
37:44Git is a tool you use to track changes, whereas GitHub is a cloud platform that allows you to store your Git repositories online and collaborate with others. To push your local project to GitHub, you'll need to link your repository to a remote.
38:01But what's a remote? Well, there are two types of repositories. Local repository is a version of a project that exists on your own machine, laptop, or whatever else you use where you do your developer work.
38:15When you initialize a repo using git init, you create a local repo in your folder. Changes you make there are private until you push them to a remote repository. So a remote repo is a version of a project stored on a server like GitHub, GitLab, or Bitbucket.
38:33It's used to share code between collaborators and keep project versions in sync across different users' computers. When collaborating with a team, you'll have two kinds of repos.
38:45Everyone in the team will have a local repository on their machine, and there will also be this one common remote repo from which everyone will sync their local repository versions. Now head over to github.com and create an account if you don't already have one.
39:01Once you're in, press the little plus icon on the top right and select new repository. Enter a repository name such as mastering git.
39:11Choose whether you wanna make it public or private. Leave the add read me file checkbox unticked, and click create repository.
39:21This is a remote repository. Here, you can see your repository's origin. Copy it.
39:28When you clone a repository from GitHub, Git automatically names the remote repository as origin by default. It's basically an alias for the remote repository's URL. Now our goal is to link our local repository to the remote origin.
39:43If you haven't yet switched the default master branch name to main, you can do that by running git branch dash m main. And this will change the branch name to main, which is a standard practice nowadays.
39:56And now we are ready to link our local repo to a remote origin. You have to run a command git remote add origin, and then you have to paste the link to the origin that you just copied and press enter.
40:10And a good thing to know is that you can have multiple remote repositories. You just have to rerun the command and change the origin name to something else. Of course, that's the name of your choice, and then you can also update the new URL.
40:25But in most cases, you'll be fine with just one remote repo. Finally, to push your local commits to GitHub, use git push dash u origin main.
40:37And remember, we used origin here to refer to the remote repository instead of typing the full URL. So press enter, and there we go.
40:46This worked. If anything with git goes wrong, typically, it goes wrong at this point when you're trying to push to a remote repo. So if you don't see what I'm seeing right here and instead you got some error, typically, all of these errors are very easily resolvable.
41:03I would just recommend copying the error message, pasting it in Google, and then fixing it right there and then. But in this case, we're good. And now if you go back to your GitHub repository and reload, boom.
41:17Your code is now online for the world or your team to see. And okay. Okay.
41:23You might have already known this. For some of you, that's about as far as you've gone with Git. Create a repo, push your changes, and call it a day.
41:33But Git has so much more to offer, especially when you're working within a team. So now let's take things up a notch and dive into branching and merging.
41:44This is where Git truly shines. Branches in Git allow you to create different versions of your project, like making a copy of a project at a specific moment in time.
41:56Whatever changes you make in this copied version won't affect the original. The main project or branch stays untouched while you experiment, modify, or add new features in the copied branch.
42:10If everything works out, you can later merge your changes back into the original project. If not, no worries. The original remains safe and unchanged.
42:20When working in a team, using separate branches for different features or bug fixes is essential. It allows you and your team to work independently on different parts of the code without causing conflicts or errors, ensuring everyone can focus on their own tasks.
42:38At the start, you'll have one default branch called main. To create a new branch, run git branch and then type a branch name. This will create a new branch.
42:50And if you wanna switch to this newly created branch, then run git checkout and then enter the branch name you wanna checkout to. And there we go. Switch to branch branch name.
43:02Now if you wanna go back to main, just run git checkout main. There we go.
43:08And here's a little pro tip. There is a shortcut to create a new branch and immediately move to it. To do that, run git checkout with a dash b flag and then enter a branch name such as feature branch.
43:24Of course, this branch name and feature branch are just dummy names. Make sure your branch name is short and explains which changes will you be making on that branch. For more tips on how to properly name your branches, you can download the git reference guide.
43:39So let's create and move to this feature branch in one command. There we go. And what I'm about to say next is very important, so keep it in mind.
43:49When you create a new branch, it'll be based on the branch you're currently on. So if you're on the main branch and run the command, the new branch will contain the code from the main branch at that point in time.
44:04However, if you're on a different branch with different code, the new branch will inherit that code instead. So to ensure you're creating the new branch from the correct starting point, you should either first switch to the branch you want to base the new one on or run this command, git branch, then you can enter a new branch name, and then the next thing can be the source branch.
44:31So if you do it this way and replace the new branch name and the source branch with the names of actual branches, then it'll create a new branch from another specific branch. So if you run this command, you can directly create and switch to a branch based on any other branch without needing to check out to it first.
44:50For now, I'll remove that. And let's say that we wanna go into our code and implement this feature we're working on. Let's say that in our case, the only feature we wanna do is to modify the readme.
45:02So below hello git, I'll say I'm adding this from feature dash branch. There we go.
45:10Feature implemented. If only it was this easy. And you can see that our IDE immediately highlighted this read me file in blue indicating that it has some changes.
45:21Now we need to add it, commit it, and push it. This time, instead of saying git add readme m d, let's just use the git add dot, which is a command that you'll use much more often. Next, we need to commit the changes with git commit dash m, and then we have to add a commit message.
45:39So this is the perfect time to learn how to write a proper commit message. A quality commit message is written in the imperative mood, a grammatical mood that sounds like you're giving a command.
45:54Like improve mobile responsiveness or add AB testing.
45:59When writing your commit message, make it answer this question. If applied to the codebase, this commit will and then fill in the blank.
46:10Like, this commit will upgrade the packages or this commit will fix thread allocation. And why do we do this?
46:19Well, because it answers the question, what will happen when I merge the branch containing this commit? It will add AB testing for example, be direct and eliminate filler words.
46:32For example, let's use modify readme in this case. It's short, sweet, and in an imperative mood.
46:40And press enter. There we go. We've just made git aware of our commit.
46:45Now that you know how to write better commits, let's take a moment and check out our remote repository. What do you think?
46:51Will it have the latest commit we made? Let's reload it, and it's the same.
46:57It doesn't contain our newly created feature branch. Do you know why? It's because the changes we made are in the local repository, which has not yet been synced with the remote repo.
47:09To see those changes, first, you'll have to publish your local branch. And you can do that using the git push dash dash set dash upstream origin, and then the name of the branch, in this case, feature dash branch, and press enter.
47:28There we go. An upstream branch is a remote branch that your local branch tracks. When you set an upstream branch using set upstream, you're essentially linking your local branch to a branch on a remote repo.
47:42Through this command, you push a local feature branch to the origin remote repository, and then you set the upstream branch for your local feature branch to track origin feature branch.
47:56Alternatively, you can also use git push dash u origin feature dash branch or the name of your branch, of course.
48:06Both set upstream and dash u establish a tracking relationship between your local branch and the remote branch.
48:15This way, in the future, if you wanna push something from your local branch to your remote branch, you simply have to run git push. That's it. At this moment, for us, everything is up to date, but as you make future changes, you don't have to rerun set upstream or dash u.
48:31You only have to add it, commit it, and push it. That's it. And if somebody else made changes to your remote branch either directly or by merging some other changes into it, you have to make your local branch up to date with the remote branch, and you do that by using the git pull command.
48:51There we go. It's already up to date in this case. This command fetches changes from the remote repo and merges them into your local repo for that branch.
49:00They're all And the story doesn't stop there. Git has plenty of advanced features like merge conflicts, reset, revert, stash, cherry pick, and more.
49:10And if you wanna truly master Git, I've got a complete crash course waiting for you on YouTube, totally free. You'll find the link down in the description. But for now, what you've learned so far is enough to kick off your DevOps journey, though it's definitely just the beginning.
49:24Make sure to keep digging deeper into Git as you grow. You'll soon use this knowledge to build pipelines that automatically build, test, and deploy code to staging or production servers. And guess what?
49:36That entire process begins with a simple git push. Git also plays a huge role in managing infrastructure. With tools like Terraform or Pulumi, your cloud setup, whether it's virtual machines, databases, or networks, lives inside a dot t f or a dot y m l files in git repository.
49:57Change a line, commit, and push, and your entire infrastructure updates automatically.
50:03All in all, in DevOps, you might not always write the application logic yourself, but you'll constantly review PRs, check configurations, and maintain secure workflows.
50:14GitHub or GitLab pull requests will become your daily workbench, and now you know exactly how they work. And once you've mastered Git, you're ready for the next big leap of building pipelines that take your commits and turn them into live runnable applications. So let's dive into that next.
50:35CICD pipelines. What are those?
50:38Well, a pipeline is just a set of automated steps that takes your code from the moment you push it all the way to production. Instead of manually running NPM install, NPM test, Docker build, and kubectl apply, every single time, your pipeline does it for you.
50:58Over the years, the industry has developed a bunch of tools for managing pipelines. Some of the most popular ones are Jenkins, which is the OG, highly customizable but self hosted and heavy, GitLab c a c d, which is built right into GitLab, great if you have your repos there, CircleCI, Travis CI, and Azure DevOps if you're on the Microsoft ecosystem.
51:23They all automate, build, test, and deploy, but most devs today prefer GitHub actions because it's deeply integrated with where your code already lives.
51:33I prefer it because it's built right into GitHub, which means no extra setup or third party logins. It's event driven, which means that it triggers on push, PRs, or cron jobs.
51:44So in simple words, when you push, your pipeline runs. It has a massive library of prebuilt actions, which means that you can just grab community workflows for testing, Docker, deployments, and more, and it has version controlled YAML configs.
51:59This is where the automation lives right within your repo, so it's easy to audit and reuse. In other words, it's super simple to use, and that's why I personally use it and recommend you do too. At its core, a GitHub actions pipeline is called a workflow.
52:16Workflows live in your repo inside the folder dot GitHub forward slash workflows, and each workflow is just a YAML file defining triggers, what events start the workflow, like pushing code or opening a PR, jobs, which are a set of tasks each running on its own virtual machine, and the steps, which are actual commands or actions executed in a job.
52:41So what is YAML? Well, YAML stands for YAML Ain't Markup Language. Funny name.
52:48Right? It basically means that YAML is not about complicated tags like HTML or XML. Instead, YAML is designed to be human friendly and machine readable.
52:59Compare that to JSON or XML, YAML is cleaner, which is why Kubernetes, Docker Compose, and GitHub Actions use it for configs.
53:08So to truly master creating pipelines with GitHub Actions, you need to understand YAML syntax rules. Unlike other file formats, YAML is white space sensitive, which means that indentation is everything.
53:22You have things like key value pairs, like language is Python. For indentation, there's no debate here.
53:29It only accepts spaces. No tabs. You can also have lists, nested structures where you can combine mappings and lists, scalars for strings, numbers, and Booleans, and even things like multiline blocks, and anchors and aliases where you can reuse different configs.
53:46You don't need every detail right now. Just focus on writing clean YAML. And similar to any other language, YAML also has specific keywords and syntax.
53:55So let me walk you through them real quick. Some of the core actions include the keyword name, which is used to define the workflow or the job title, on, which defines triggers such as push, PR, or schedule, jobs to define jobs, their OS, and steps, steps, which are sequential commands or actions, run, which includes shell commands to execute, uses if you wanna use prebuilt actions, with where you can pass params to actions, e n v to set environment variables, and needs to make one job depend on the other.
54:32List can go on and on, but these are some special keywords that matter most in GitHub actions YAML files. And the list could go on and on. And that's why I prepared a complete YAML and GitHub actions CICD cheat sheet.
54:46It's packed with so many useful commands. And hey, if you'd like to buy me a coffee and support me in making more high quality YouTube videos like this one, plus get a detailed cheat sheet on YAML and CICD pipelines, you can join our new channel membership. No pressure at all.
55:02The link should be just somewhere below this video. Alright. Let's keep going.
55:08For now, let's move forward and put what we've learned into action by creating our first GitHub actions pipeline using YAML.
55:17I'll start with creating a new GitHub repo. Let's call it something like DevOps pipelines.
55:23Create a new repo, and then go ahead and clone it locally by copying this URL and then just clone it.
55:30In this case, I'll be using WebStorm to clone it automatically. Once you're in, you'll have an empty repo. So what you can do is initialize a project by running n p m init dash y, and this will give you a new package JSON with a new node project.
55:48Alongside it, go ahead and create a new file called index dot j s, which will act as the starting point of our application. And in there, add a new console log saying, hello, DevOps.
56:02We can also add maybe an additional console log saying something along the lines of I'm learning CICD using GitHub actions.
56:14Perfect. And what we can do is also create another file called test dot j s. Within it, I'll add a console log when we start the tests.
56:26So I'll say starting tests, and I'll also add a time out right here that'll wait for three seconds.
56:33So say console dot log waiting three seconds, and, of course, it'll take three thousand milliseconds, which is three seconds.
56:43And then I'll add another console log saying something like tests complete. Now we can add both of these files as scripts within package JSON. So right here, instead of test, I'll simply add a run script, which will run node index dot j s, and I'll also add a test script, which will also run the test dot j s file.
57:08Now we wanna create a special folder. Right here in the root of our application, create a new folder that's called dot GitHub.
57:17And within dot GitHub, create a new folder called workflows. Within workflows, you can see how my IDE automatically recognizes that I might wanna do a GitHub workflow, which would create a workflow dot YAML file.
57:31But in this case, we can create it manually by creating a new pipeline dot YAML, and we can start creating it.
57:39First, it needs a normal human readable name. So I'll say name is c I pipeline, which will make it easier to identify this pipeline in GitHub actions tab as you create more pipelines. Then it needs an on property, which will define the trigger event.
57:56What action in GitHub should start this pipeline? In this case, we can say that it'll be a push action, so whenever somebody pushes code to the repo, and then we only wanna restrict it when changes are pushed to the main branch.
58:12So say branches is gonna be set to an array of main.
58:17So if you push to a feature branch, nothing happens. Only when you merge into main, which is our production ready branch, the pipeline will run. This will prevent the unnecessary builds for every branch and ensure that main always stays tested.
58:32After that, we can define the jobs. A job is a group of steps that run together. You can think of it like a container in which multiple actions are executed in sequence.
58:44In this case, we will name this job build, and we have to define what it will run on. So in this case, we'll tell it to use runs on the latest fresh Ubuntu latest machine.
59:00Why Ubuntu? Because it's fast, lightweight, and widely supported. Then inside a job, you have to define different steps.
59:09These are the actual instructions to be executed one after another. Each step has a name. In this case, we'll call it checkout code.
59:20And then you can say what it uses. So in this case, you can say uses actions forward slash checkout at v five.
59:30This means that we're using a prebuilt GitHub action, such as this one right here. GitHub actions checkout, and this is the action we're using that checks out our repository under the GitHub workspace so our workflow can access it.
59:45Now we can add some more steps, such as another one with the name of set up Node JS, which will also use so uses another predefined action of actions forward slash setup dash node at v three Width, and here you can define the node version that you wanna set it up with.
1:00:08In this case, let's do 16. After that, we wanna run some dependencies, so I will create another package that'll have a name of install dependencies.
1:00:21And the only thing that it'll do is it'll run NPM install. And after that, we'll have another one which will run the tests. So it'll be a step of name run tests, and it'll run NPM test.
1:00:37This is typically done using Jest or some other testing framework, but for now, we'll just run our manual test file. Now as you can see, we have some yellow squiggly lines right here saying that we have some issues with our setup, and that's because I used tabs for indentation instead of using spaces.
1:00:54So after fixing the indentation, it should look something like this. You should have two spaces for each indentation point. We have jobs, then the build, then we have runs on, and then steps is directly below runs on, two spaces indented after build.
1:01:10Perfect. Now let's go ahead and add and commit those changes to GitHub by running git add dot git commit dash m feature add c I pipeline and then git push.
1:01:26Now if you head back over to your GitHub repo, you'll be able to see your latest changes. And now if you head over to the actions tab, you'll be able to see your newly created pipeline right here. Expand it, and you'll see all the job details if you enter the build, such as setting up the job, setting up Node.
1:01:43Js, installing dependencies, and then even running tests. And you can see the results of those tests right here.
1:01:49Starting, completed, waiting three seconds, and the job finally is done. Now this pipeline will run on every single main push.
1:02:00I can prove that to you by heading over to the code and adding a README, which will automatically push a new readme.md file to main. So I'll say testing the CI pipeline and commit and push.
1:02:16Since a push has been made, if you head over to actions, you'll see that a new action run automatically based off of my commit, which pushed over to main.
1:02:26It'll rerun the tests for the entire application and make sure that everything is good. From here on, you can add additional test coverage checks, deploy to staging or production, or find actions from the GitHub marketplace where there are so many different actions that you can immediately use within your application.
1:02:43So in the build and deploy stage, this allows you to see your CICD pipelines, which enforce code quality, passing tests, and shipping inside Docker container.
1:02:53This was just one simple example. Based off of this, you can create more workflows or reuse existing workflows from the community. Later on in the build and deploy stage of this project, you'll actually set up real CICD pipelines for our acquisitions application ensuring that your formatting is consistent, your tests pass successfully, and everything runs smoothly within a Docker container.
1:03:17Speaking of which, let's dive into Docker next.
1:03:23It works on my machine. Have you ever heard or said this? Or it works in Windows but not Mac OS?
1:03:30Or have you ever struggled with juggling different Node JS versions for different projects? This is why Docker was created in 2013. And it's not just a tool to solve compatibility issues.
1:03:42It's a critical skill required for the highest paying jobs as surveys find Docker to be the most popular tool used by 57% of professional developers. If you don't learn it now, you significantly lower your chances of landing a job.
1:03:58You can think of Docker as a lunchbox for our application. In the lunchbox, we pack not just the main dish, which is our code, but also all the specific ingredients or dependencies it needs to taste just right.
1:04:13Now this special lunchbox is also magical. It doesn't matter where we want to eat, at our desk, a colleague's desk, or have a little picnic.
1:04:24No matter the environment or different computers, wherever we open the lunchbox everything is set up just like it is in our kitchen. It ensures consistency, portability and prevents us from overlooking any key ingredients making sure our code runs smoothly in any environment without surprises.
1:04:45Technically, that's what Docker is. It's a platform that enables the development, packaging, and execution of applications in a unified environment.
1:04:55By clearly specifying our application's requirements such as Node. Js versions and necessary packages, Docker generates a self contained box that includes its own operating system and all the components essential for running our application.
1:05:11This box acts like a separate computer virtually providing the operating system, run times, and everything required for our application to run smoothly. But why should we bother using Docker at all? Big shots like eBay, Spotify, Washington Post, Yelp and Uber noticed that using Docker made their apps better and faster in terms of both development and deployment.
1:05:40Uber for example said in their study that Docker helped them onboard new developers in minutes instead of weeks. So what are some of the most common things that Docker helps with? First of all, consistency across environments.
1:05:56Docker ensures that our app runs the same on my computer, your computer and your boss's computer. No more it works on my machine drama.
1:06:07It also means everyone uses the same commands to run the app no matter what computer they're using. Since downloading services like Node.
1:06:16Js isn't the same on Linux, Windows, or Mac OS, developers usually have to deal with different operating systems. Docker takes care of all of that for us.
1:06:27This keeps everyone on the same page, reduces confusion, and boosts collaboration making our app development and deployment faster.
1:06:36The second thing is isolation. Docker maintains a clear boundary between our app and its dependencies. So we'll have no more clashes between applications much like neatly partitioned lunchbox compartments for veggies, fruits, and bread.
1:06:52This improves security, simplifies debugging, and makes development process smoother.
1:06:58Next thing is portability. Docker lets us easily move our applications between different stages like from development to testing or testing to production. It's like packaging your app in a lunchbox that can be moved around without any hassle.
1:07:14Docker containers are also lightweight and share the host system resources making them more efficient than any traditional virtual machines.
1:07:24This efficiency translates to faster application start times and reduced resource usage. It also helps with version control as just like we track versions of our code using Git, Docker helps us track versions of our application.
1:07:38It's like having a rewind button for our app so we can return to a previous version if something goes wrong. Talking about scalability, Docker makes it easy to handle more users by creating copies of our application when needed.
1:07:54It's like having multiple copies of a restaurant menu. When there are more customers, each menu serves one table.
1:08:00And finally, DevOps integration. Docker bridges the gap between development and operations, streamlining the workflow from coding to deployment.
1:08:11This integration ensures that the software is developed, tested, and deployed efficiently with continuous feedback and collaboration. How does Docker work?
1:08:22There are two most important concepts in Docker, images and containers, and the entire workflow revolves around them. Let's start with images.
1:08:34A Docker image is a lightweight standalone executable package that includes everything needed to run a piece of software, including the code, runtimes like Node.
1:08:46Js, libraries, system tools, and even the operating system.
1:08:50Think of a Docker image as a recipe for our application. It not only lists the ingredients being code and libraries, but also provides the instructions such as runtime and system tools to create a specific meal, meaning to run our application.
1:09:08And we would wanna run this image somewhere. Right? And that's where containers come in.
1:09:13A Docker container is a runnable instance of a Docker image. It represents the execution environment for a specific application, including its code, runtime, system tools, and libraries included into Docker image.
1:09:29A container takes everything specified in the image and follows its instructions by executing necessary commands, downloading packages, and setting things up to run our application.
1:09:40Once again, imagine having a recipe for a delicious cake, the recipe being the docker image. Now when we actually bake the ingredients, we can serve it as a cake.
1:09:51Right? The baked cake is like a docker container. It's the real thing created from the recipe.
1:09:58Just like we can have multiple servings of the same meal from a single recipe or multiple documents created from a single database schema, we can run multiple containers from a single image.
1:10:10That's what makes Docker the best. We create one image and get as many instances as we want from it in form of containers. Now if you dive deeper into Docker, you'll also hear people talk about volumes.
1:10:23A Docker volume is a persistent data storage mechanism that allows data to be shared between a Docker container and the host machine, which is usually a computer or a server or even among multiple containers. It ensures data durability and persistence even if the container is stopped or removed.
1:10:45Think of it as a shared folder or a storage compartment that exists outside the container. The next concept is Docker network.
1:10:54It's a communication channel that enables different Docker containers to talk to each other or with the external world. It creates connectivity, allowing containers to share information and services while maintaining isolation.
1:11:09Think of a Docker network as a big restaurant kitchen. In a large kitchen being the host, you have different cooking stations or containers, each focused on a specific meal, meal being our application.
1:11:23Each cooking station or a container is like a chef working independently on a meal. Now imagine a system of order tickets or a docker network connecting all of these cooking stations together. Chefs can communicate, ask for ingredients, or share recipes seamlessly.
1:11:43Even though each station or a container has its own space and focus, the communication system or the Docker network enables them to collaborate efficiently. They share information without interfering with each other's cooking process.
1:11:58I hope it makes sense, but don't worry if it doesn't. We'll explore it together in the demo. So moving on, the Docker workflow is distributed into three parts, Docker client, Docker host aka Docker daemon, and Docker registry aka Docker hub.
1:12:17The Docker client is the user interface for interacting with Docker. It's the tool we use to give Docker commands. We issue commands to the Docker client via the command line or a graphical user interface, instructing it to build, run, or manage images or containers.
1:12:36Think of the Docker client as the chef giving instructions to the kitchen staff. The Docker host or Docker daemon is the background process responsible for managing containers on the host system.
1:12:49It listens for Docker client commands, creates and manages containers, builds images, and handles other Docker related tasks.
1:12:58Imagine the Docker host as the master chef overseeing the kitchen, carrying out instructions given by the chef or the Docker client. Finally, the Docker registry aka Docker Hub is a centralized repository of Docker images.
1:13:15It hosts both public and private registries or packages. Docker is the Docker Hub, what Git is the GitHub in a nutshell. Docker images are stored in these registries and when you run a container, Docker may pull the required image from the registry if it's unavailable locally.
1:13:35To return to our cooking analogy, think of Docker registry as a cookbook or recipe library. It's like a popular cookbook store where you can find and share different recipes, in this case, Docker images.
1:13:49In essence, the Docker client is the command center where we issue instructions. The Docker host then executes these instructions and manages containers, and the Docker registry serves as a centralized storage for sharing and distributing images.
1:14:07Using Docker is super simple. All you have to do is click the link in the description, download Docker desktop for your own operating system, and that will help you containerize your application in the easiest way possible.
1:14:23It'll definitely take some time to download, but once you're there, you can accept the recommended settings and sign up. Once you're in, on the left side, you can see the links to containers, which display the containers we've made, images, which shows the images we've built, and volumes, which shows the shared volumes we have created for our containers, and other beta features like builds, dev environments, and Docker Scout.
1:14:54Now return to the browser and Google Docker Hub. The first result will surely be hub.docker.com. And then open it up, go to explore, and you can see all of the public images created so far by developers worldwide.
1:15:12From official images by verified publishers to sponsored open source ones covering everything from operating system images like Ubuntu, languages like Python and Golang, databases like Redis, Postgres, Mongo for MongoDB, MySQL, runtimes like Node.
1:15:30Js to even hello world Docker image and also the old peeps like WordPress and PHP. Almost everything that you need is right here. But how do we create our own Docker images?
1:15:45Easy peasy. Creating a Docker image starts from a special file called Dockerfile. It's a set of instructions telling Docker how to build an image for your application.
1:15:57There are some specific instructions and keywords we use to tell Docker what we want through the Dockerfile. Think of it as Docker syntax or language to specify exactly what we want.
1:16:10Here are some of the commands. From specifies the base image to use for the new image. It's like picking a starting kitchen that already has some basic tools and ingredients.
1:16:21WorkDear sets the working directory for the following instructions. It's like deciding where in the kitchen you want to do all your cooking. Copy copies the files or directories from the build context to the image.
1:16:36It's like bringing in your recipe, ingredients, and any special tools into your chosen cooking spot. Run executes commands in the shell during image build.
1:16:46It's like doing specific steps of your recipe such as mixing ingredients. Expose informs Docker that the container will listen on specified network ports at run time. It's like saying, I'm going to use this specific part of the kitchen to serve the food.
1:17:04ENV sets environment variables during the build process. You can think of that as setting the kitchen environment, such as deciding whether it's a busy restaurant or a quiet home kitchen.
1:17:15ARG defines build time variables. It's like having a note that you can change before you start cooking, like deciding if you want to use fresh or frozen ingredients.
1:17:26Volume creates a mount point for externally mounted volumes, essentially specifying a location inside your container where you can connect external storage.
1:17:38It's like leaving a designated space in your kitchen for someone to bring in extra supplies if needed. CMD provides default command to execute when the container starts.
1:17:48It's like specifying what dish you want to make when someone orders from your menu. Entry point specifies the default executable to be run when the container starts. It's like having a default dish on your menu that people will get unless they specifically ask for something else.
1:18:06And you might wonder, isn't entry point the same as CMD? Well, not really. In simple terms, both CMD and entry point are instructions in Docker for defining the default command to run when a container starts.
1:18:23The key difference is that CMD is more flexible and can be overwritten when running the container, while entry point defines the main command that cannot be easily overwritten. Think of CMD as providing a default which can be changed and entry point as setting a fixed starting point for your container.
1:18:44If both are used, CMD arguments will be passed to entry point. And these are the most used keywords when creating a Dockerfile. I have also prepared a list of other options you can use in Dockerfiles.
1:18:57You can think of it as a complete guide and a cheat sheet you can refer to when using Docker. The link is in the description. But now let's actually use some of these commands in practice.
1:19:09Let's try to run one of the images listed in the Docker Hub to see how that works. Let's choose one of the operating system images as an example. Let's go for Ubuntu.
1:19:20On the right side of the details of the image, you'll see a command. Copy it and try executing it in your terminal. But before we paste it, first create a new empty folder on our desktop called docker underscore course, and then drag and drop it to our empty Visual Studio Code window.
1:19:39Open up your empty terminal and paste the command docker pull ubuntu. It's going to do it using the default tag latest, and it's gonna take some time to pull it.
1:19:50As you can see, it's working. Docker initially checks if there are any images with that name on our machine. If not, it searches for the Docker Hub, finds the image, and automatically installs it on our machine.
1:20:05Now if we go back to Docker desktop, we'll immediately see an Ubuntu image right here under images. To confirm that we actually installed a whole different operating system, we can run a command that executes the image.
1:20:19Do you know how that process is called? Creating a container. So let's run docker run dash I t for interactive and then Ubuntu and press enter.
1:20:33After you run this command, head over to Docker desktop and if you go to containers, you'll see a new container based off of the Ubuntu image.
1:20:43Coming back to our terminal, you'll see something different. If you've ever tried Ubuntu before, you'll notice that this terminal looks exactly like the Ubuntu command line. Let's test out some of the commands.
1:20:55LS for list. We have c d home to move to our home directory. M k dir, which is going to create a new directory called hello.
1:21:05We can once again l s c d into hello to navigate to it. We can create a new hello dash ubuntu.txt.
1:21:17We can l s to check it out if it's there. And it is. We have just used different Ubuntu commands right here within our terminal.
1:21:26Amazing. Isn't it? We are running an entirely different operating system simply by executing a Docker image within a Docker container.
1:21:36For now, let's kill this terminal by pressing this trash icon and navigate back to our Docker desktop. Now a bigger question awaits.
1:21:45How do we create our own Docker images? We can start from a super simple Docker app that says hello world. Let's create a new folder called hello dash Docker.
1:22:00Within it, we can create a simple hello dot JS file. And we can type something like console dot log hello Docker.
1:22:12Then comes the interesting part. Next, we'll create a Dockerfile. Yep.
1:22:18It's just Dockerfile like this. No dots.
1:22:22No extensions. Versus Code might prompt you to install a Docker extension. And if it does, just go ahead and install it.
1:22:30Now let's figure out what goes into the Docker file. Do you remember the special Docker syntax we talked about earlier? Well, let's put it to use.
1:22:40First, we have to select the base image to run the app. We want to run a JavaScript file so we can use the node runtime from the Docker Hub. We'll use this one with an Alpine version.
1:22:51It's a lightweight version of Linux. So we can type something like from node 20 dash Alpine.
1:23:02Next, we want to set the working directory to forward slash app. This is the directory where commands will be run. And then forward slash app is a standard convention.
1:23:12So we can type work there and then type forward slash app. Next, we can write copy dot dot like this.
1:23:22This will copy everything from our current directory to the Docker image. The first dot is the current directory on our machine and the second dot is the path to the current directory within the container.
1:23:37Next, we have to specify the command to run the app. In this case, c m d node hello dot j s will do the trick.
1:23:47And now that we have created our Dockerfile, let's move into the folder where the Dockerfile is located by opening up the terminal and then running c d hello dash docker.
1:23:59Inside of here, let's type docker build dash t and t stands for the tag which is optional and if no tag is provided, it defaults to the latest tag.
1:24:11And finally, the path to the Dockerfile And in this case, that's hello dash Docker dot because we are right there.
1:24:20And press enter. It's building it and I think it succeeded. Great.
1:24:26To verify that the image has been created or not, we can run a command docker images. And you can see that we have two images, Ubuntu as well as hello docker created sixteen seconds ago.
1:24:42Now if you're a more visual person, you can also visit Docker desktop. Here, if you head to images, you can see all of the images we have created so far. Now that we have our image, let's run or containerize it to see what happens.
1:24:58So if we go back, we can run docker run hello dash docker.
1:25:04There we have it, an excellent console log. If we go back to Docker desktop and then open up that container and navigate inside of the files, you'll see a lot of different files and folders, but there is one special file here.
1:25:23Wanna make a guess? Yes. It's app, which we created in Dockerfile.
1:25:28Moving inside it, we can see that it contains two of the same files we have in our application, Dockerfile and HelloJS, exact replica.
1:25:37Also, if we want to open up our application in shell mode similar to what we did with Ubuntu, we have to run docker run I t hello dash docker s h.
1:25:50This puts us directly within the operating system and then you can simply run node hello dot j s to see the same output. We can also publish the images we have created on Docker, but before that, let's build something a bit more complex than the simple hello world and then let's publish it to the Docker Hub, which means that now we're diving into the real deal, dockerizing React JS applications.
1:26:20Let's dockerize our first React application. I'm gonna do that by quickly spinning up a simple React project by running the command n p m create vite at latest and then React dash Docker as the folder name.
1:26:36If you press enter, it's going to ask you which flavor of JavaScript you want. In this case, let's go with React. Sure.
1:26:44We can use TypeScript and we can now c d into React dash Docker and we won't run any npm install or npm run dev because the dependencies will be installed within our dockerized container. So with that said, now if we clear it, we are within react docker and you can see our new React application right here.
1:27:06So as the last time, you already know the drill. We need to create a new file called Docker file.
1:27:12As you can see, it automatically gets this icon and it's going to be quite similar to the original Docker file that we had, but this time I wanna go into more depth about each of these commands so you know exactly what did they do. And because of that, below this course, you can find a complete Dockerfile for our React Docker application.
1:27:34Copy it and paste it here. Once you do that, you should be able to see something that looks like this. It seems like there's a lot of stuff, but there really isn't.
1:27:44It's just a couple of commands, but I wanted to take my time to deeply explain all of the commands we're using right here. So let's go over all of that together.
1:27:55First, we need to set the base image to create the image for React app, and we are setting it up from Node 20 Alpine.
1:28:04It's just a version 20 of Node. You can use any other version you want. And in these courses, I wanna teach you how to think for yourself, not necessarily just replicate what I'm doing here.
1:28:15So if you hover over the command, you can see exactly what it does. Set the base image to use for subsequent instructions. From must be the first instruction in a Docker file.
1:28:26And you can see a couple of examples. You can use a from base image or you can even add a tag or a digest. In this case, we're adding a tag of a specific version, but it's not necessary.
1:28:38And if you click online documentation, you can find even more instructions on exactly how you can use this command. Next, we have to play with permissions a bit.
1:28:48Now I know that these couple of commands could be a bit confusing, but we're doing it to protect our new container from bad actors and users wanting to do something bad with it. So because of that, we create a new user with permissions only to run the app.
1:29:05The dot s is used to create a system user and dot g is used to add that user to a group. This is done to avoid running the app as a root user that has access to everything. That way, any vulnerability in the app can be exploited to gain access to the whole system.
1:29:23This is definitely not mandatory, but it's definitely a good practice to run the app as a non root user, which is exactly what we're doing here. We're creating a system user, adding it to the user group, and then we set the user to run the app, user app, and you can see more information about right here.
1:29:41Set the username to use when running the image. Next, we set the working directory to forward slash app and then we copy the package JSON and package log JSON to the working directory.
1:29:55This is done before copying the rest of the files to take advantage of Docker's cache. If the package JSON and package log JSON files haven't changed, Docker will use the cache dependencies. So copy files or folders from source to destination in the images file system.
1:30:14So first, you specify what you wanna copy from the source and then you provide a path where you wanna paste it to. Next, sometimes the ownership of the files in the working system is changed to root and thus the app can't access the files and throws an error e access permission denied. To avoid this, change the ownership of the files to the root user.
1:30:37So we're just changing it back from what we did above. Then we change the ownership of the app directory to the app user by running a new command, in this case, c h own where we specify which user and group and directory we're changing the access to, and then we change the user back to the app user.
1:30:55And once again, if these commands are not a 100% clear, no worries. This is just about playing with user permissions to not let bad actors play with our container. Finally, we install dependencies, copy the rest of the files to the working directory, expose the port five one seven three to tell Docker that the container listens on that specified network, and then we run the app.
1:31:17If you wanna learn about any of these commands, hover over it, you can already get a lot of info, and then go to online documentation if you need even more. With that said, that is our Dockerfile.
1:31:29Another great practice that we can do is just go right here and create another file similar to dot git ignore. This time it's called dot docker ignore.
1:31:40And here you can add node underscore modules just to simply exclude it from Docker because we don't really need it in our node modules on our GitHub. We don't need it anywhere, not even in Docker.
1:31:52Docker is playing with our package JSON and package lock JSON and then rebuilds it when it needs to. Now finally, once we have our Docker file, we are ready to build it. We can do that by opening up a new terminal, navigating to React Docker, and we can build it by running the command Docker build dash t for tag, which we can leave as default, React dash Docker, which is the name of the image, and then dot to indicate that it's in the current directory.
1:32:22And finally, press enter. This is going to build out the image, but we already know that an image is not too much on its own.
1:32:31To use the image, we have to actually run it. So let's run it by running the command docker run react dash docker and press enter.
1:32:41As you can see, it built out all of the packages needed to run our app and it seems to be running on localhost five one seven three. But if we open it up, it looks like the site isn't showing even though we specified that expose endpoint right here saying that we're listening on 5 one seven three.
1:33:01So why is it not working? Well, first, we need to understand that expose does only one job and it's to inform Docker that the container should listen to that specific exposed port in runtime.
1:33:14That does make sense, but then why then work? Well, it's because we know on which port the Docker container will listen to. Docker knows it and so does the container, but someone is missing that information.
1:33:28Any guesses? Well, it's the host, it's the main computer we're using to run it. As we know, containers run-in isolated environments and by default they don't expose their ports to the host machine or anyone else.
1:33:43This means that even if a process inside the container is listening on a specific port, the port is not accessible from outside the container. And to make our host machine aware of that, we have to utilize a concept known as port mapping. It's a concept in Docker that allows us to map ports between the Docker container and the host machine.
1:34:05It's exactly what we wanna do. So to do that, let's kill our entire terminal by pressing this trash icon, reopen it, renavigate to react dash Docker and let's run the same command docker run, and then we're gonna add a p flag right here and say map five one seven three in our container to 5173 on our host machine and then specify which image do we wanna run and press enter.
1:34:35Now as you can see, it seems to be good, but if I run it, same things happens again. It's not Docker's fault, but it's something that we missed.
1:34:46It's a Vite. If you read the logs right here, it's gonna say use dash dash host to expose. So we have to expose that port for Vite two.
1:34:57So let's modify our package JSON by going right here and adding the dash dash host to expose our dev environment.
1:35:07And now again, we'll have to stop everything, kill our terminal, reopen it, renavigate to React Docker and then run the image again, which makes you wonder, wouldn't it be great if Docker does it on its own whenever we make some file changes?
1:35:24And the answer is yes, definitely. And Docker heard us.
1:35:28Later in the course, I'll teach you how to use the latest Docker features that allow us to automatically build images and save us from all of this hassle.
1:35:39But I first wanna teach you how to do it manually to understand how cool Docker Compose is, which I'm gonna teach you later on. So let's just rerun the same command. And now we get an error.
1:35:52This means that something is already connected to that port. And this indeed is true if you check out our containers or images. We have accumulated a large number of images.
1:36:05So let's do a quick practice on how to clear out all of our images or containers. Back in our terminal, we can run a command docker p s, which is going to give us a list of all of the current containers alongside their IDs, images, created status, and more as well as in which ports are they listening on.
1:36:26This is for all the active running containers. And if we wanna get absolutely all containers, we can run docker p s dash a, and here you can see absolutely all containers that we have.
1:36:39That's a lot. Now the question is how do we stop a specific container? Well, we can stop it by running docker stop and then use the name or the ID of a specific container.
1:36:51You can use the first three digits of the container ID or you can use the entire name. So let's use C three D.
1:36:58C three d. And if you get back the same command, it means that it successfully stopped it. If we go back to containers, you can see that the c three d is no longer running.
1:37:11But now let's say we have accumulated a large number of containers, which we indeed have both the images and containers. So how can we get rid of all of the inactive containers we have created so far?
1:37:23Well, we can do that by running docker container prune. If you run that, it's gonna say this will remove all stopped containers, so let's press y, and that's fine.
1:37:34We only had one that was stopped that we manually stopped and it pruned it. But you can also use the command docker r m to remove a specific container by name or its ID. So let's try with this one, AA7, docker R m a a seven and press enter.
1:37:55Here we get a response saying that we cannot stop a running container. Of course, you could always use the dash dash force and that's gonna kill it. We can verify right here.
1:38:06These commands are great and it's always great to know your way around the CLI, But nowadays, we also have Docker desktop, which allows us to play with it within a graphical user interface, which makes things so much simpler.
1:38:20You can simply use the stop action to stop the container or you can use the delete action to delete a container. It is that easy.
1:38:29Similarly, you can do that for images by selecting it and deleting all images. And you can follow my process of deleting everything right now.
1:38:38I just wanna ensure that we have a clean working environment before we build out our React example one more time. And while we're here, if you have any volumes, feel free to delete those as well.
1:38:50There we go. So moving back, we wanna first build out our image And now let's repeat how to do that. You simply have to run docker build dash t, the name of the image, and then dot.
1:39:05This is going to build out the image. After you do that, we have to run it with port mapping included, so that's going to be docker run dash p, map the ports, and then the name of the container you wanna run, and press enter.
1:39:21It's going to run it, and you can see a bit of a difference right now here. It's exposed to the network, and if you try to run localhost five one seven three, you can see that this time it actually works.
1:39:34That's great. But now if we go back to our code, go to source, app, and change this Vite and React to something like Docker is awesome and save it, Back on our local host, you can see that it didn't make any changes.
1:39:56That's very unfortunate. We hope that this container could somehow stay up to date with what we are developing. Otherwise, it would be such a pain to constantly rebuild containers with new changes.
1:40:09This happens because when we build the Docker image and run the container, the code is then copied into that container. You can see all the files right here and they're not gonna change.
1:40:21So even if you go right here to app and then source and then app TSX, right click it and click edit file, you'll be able to see that here it still says Vite plus React. So what can we do?
1:40:36Well, we'll have to further adjust our command. So let's simply stop our active container so we can then rerun a new one on the same port. Let's go back to our Visual Studio Code, clear it, make sure that you're in the react dash docker folder, and we need to run the same command.
1:40:54Then we have to also add a string sign, dollar sign, p w d, close it, and then say colon forward slash app and close it like so.
1:41:06It seems a bit complicated, doesn't it? What this means is that we tell Docker to mount the current working directory where we run the Docker run command into the app directory inside the container. This effectively means that our local code is linked to the container and any changes we make locally will be immediately reflected inside the running container.
1:41:31This tiny PWD represents the current working directory over here. It executes in the runtime to provide the current working directory path.
1:41:40And v? V stands for volume. That's because we're creating a volume that's gonna keep track of all of those changes.
1:41:47Remember that we talked about volumes before? They try to ensure that we always have our data stored somewhere. But before you go ahead and press enter, there's one more additional flag that we have to add to this command and that is yet another dash v, but this time forward slash app forward slash node underscore modules.
1:42:09Why are we doing this? Well, we have to create a new volume for the node modules directory within the container. We do this to ensure that the volume mount is available in its container.
1:42:21So now when we run the container, it will use the existing node modules from the named volume, and any changes to the dependencies won't require a reinstall when starting the container.
1:42:33This is particularly useful in development scenarios where you frequently start and stop containers during code changes. So let's run it. It's running on local host five one seven three.
1:42:46Docker is indeed awesome, but now the question is if we change it, what's gonna happen? So we go here and say something like Docker is awesome, but also add a couple of whales at the end, press save, and then you can see PMVIT update source app TSX, and now if we run it, we have a couple of whales right here.
1:43:10There we go. So whenever you change something, you'll see the result instantly in the UI. That's amazing.
1:43:18And even if we go back to our Docker desktop, you can see that now we have a volume that keeps track of these changes and if you go under containers, go to our active container, go to files, and then let's go to app, source, app TSX and edit, you can see that the changes are also reflected right here.
1:43:40So that's it. You have successfully learned how to dockerize a front end application. Not many developers can do that, but you, you are just getting started.
1:43:53Now that we have created our docker image, let me teach you how to publish it. We can do that using the command line. So let's go right here, kill our current terminal, reopen it, and c d into React Docker.
1:44:09Next, we can run Docker login. And if you already logged in with Docker desktop, it should automatically authenticate you. Next, we can publish our image using this command, docker tag react dash docker, then you need to add your username and the name of the image.
1:44:31You can find your username by going to Docker desktop, clicking on the icon on top right, and then copying it from there. In my case, it's JavaScript mastery, and then I'm gonna do forward slash react dash docker.
1:44:44It's okay if we don't provide any tag right here as the default tag is going to be colon latest. Also, don't forget that below this course, I provided a complete list of all of the commands including different tag commands to help you get started with Docker anytime, anywhere. So check them out and try running some of them.
1:45:05Finally, let's publish our image. And now we have to run docker push JavaScript mastery or in this case your username forward slash react dash docker.
1:45:17And this is going to actually push it to our Docker hub. There we go. Now if you go back to Docker desktop, you can see that we have a JavaScript mastery React Docker image that is now actually pushed on the hub.
1:45:32And you can also check it out right here by going to local hub images and then you can see JavaScript mastery has one latest image. And another cool thing you can do is go to hub.docker.com where you can find your image published under repositories and then check out your account right here and you'll be able to see your React Docker image right here live on Docker Hub.
1:45:59And now other people can run this image as well and containerize their applications by using it. How cool is that? And that's all it is to it.
1:46:09You have successfully published your first Docker image. But now that you know the basis, let's find a more efficient way of dockerizing our applications. Oh yeah, developers are lazy.
1:46:23So writing and running all of these commands for building images and containers and then mapping them to host is just too much to do. But it's not the only way. We can improve or automate this process with Docker Compose and run everything our application needs to run through Docker using one small single command.
1:46:46Yes. We can use a single straightforward command to run the entire application. So say hi to Docker Compose.
1:46:56It's a tool that allows us to define and manage multi container Docker applications. It uses a YAML file to configure the services, networks, and volumes for your application, enabling us to run and scale the entire application with a single command.
1:47:14We don't have to run 10 commands separately to run 10 containers for one application, thanks to Docker Compose. We can list all the information needed to run these 10 containers or more in a single file and then run only one command that automatically triggers running the rest of the containers.
1:47:34In simple words, Docker Compose is like a chef's recipe for preparing multiple meals in a single dinner. It allows us to define and manage the entire cooking process for recipes in one go, specifying ingredients, cooking instructions, and how different parts of the meal should interact.
1:47:55With Docker Compose, we can serve up our entire culinary experience with just one command.
1:48:03And while we can manually create these files on our own and set things up, Docker also provides us with a CLI that generates these files for us. It's called Docker init. Using Docker init, we initialize our application with all the files needed to dockerize it by specifying our tech choices.
1:48:25So let's go ahead and create another VIT project which we can use to test out the features of Docker Compose and Docker in it. We can open up a terminal and then run npm create vite add latest.
1:48:40In this case, we can call it vite dash project, and press enter. It's going to ask us a couple of questions.
1:48:47It can be a React TypeScript application. We can CD into it and please make sure that you are in the Docker course, meaning in the root of our folder. So it needs to create it right next to React.
1:49:02If you were in React before when you run this command, it's going to create it inside of it. If that's the case, delete it and just navigate to Docker course and then run the command. Now we can c d into Vite project and we can learn how to use Docker in it.
1:49:18It's so simple. You simply run Docker in it.
1:49:22That's all there is to it. And it's gonna ask you many questions based off which it's going to generate a perfect YAML file for you. So what application platform are we planning on using?
1:49:33In this case, it's gonna be Node, so you can simply press enter. What version? You can just press enter one more time to verify what they're saying in parentheses.
1:49:4320 is fine with us. NPM is good. Do we wanna use NPM run build?
1:49:49No, actually. Uh, in this case, we're gonna say no, and we're gonna say NPM run dev.
1:49:55That's what we wanna use. And the server is going to be 5173, and that's it.
1:50:03We can see that this has generated three new files for us. The Dockerfile, which we already know a lot about, this one has some specific details in it, but you can see that, again, it's based off of the same thing.
1:50:17It starts from a specific version, sets up the environment variables, sets up the working directory, and runs some commands. We also have a Docker ignore where we can ignore some additional files, and then there's this new file, compose dot YAML.
1:50:33While all of these files are important, with using Docker Compose, YAML is the most important one. And you can read all of these comments, but for now, I just wanna delete them to show you what it is comprised of.
1:50:47We simply define which services we want our apps or containers to use. We have a server app where we build the context, specify environment variables, and specify the ports.
1:50:59Of course, these can get much more complicated in case you have multiple services you wanna run, which is exactly what I wanna teach you right now. Here, they were even kind enough to provide an example of how you would do that with running a complete Postgres database. So you can specify the database, database image, and additional commands you can run, but more on that later, we're gonna approach it from scratch.
1:51:25For now, we can leave this empty composed YAML, And first, let's focus on just the regular Dockerfile. In this case, we can replace this Dockerfile with the one we have in a React Docker application.
1:51:38So copy this one right here and paste it into this new one. This one we already know what it is doing. Now moving inside of the YAML file, here we can rename the server into web as that's a common practice for running web applications and not servers.
1:51:59We can also remove environment variables as we're not using any, and we can leave the port. Finally, we need to add the volumes for our web service.
1:52:10So we can save volumes. Make sure to provide a space here and then a dash, and that's gonna be dot colon forward slash app and another dash forward slash app forward slash node modules.
1:52:24Does this ring a bell? It's similar what we have done before manually by using the docker build command, but now we're doing it all with this compose YAML file. And now all we have to do is run a new command, docker compose up and press enter.
1:52:43And as you can see, we get a permission denied. You never wanna see this. If you're in Windows, maybe you're used to seeing this every day, in which case you simply have to close Visual Studio Code, right click it, and then press run as administrator.
1:53:00That should give you all the necessary permissions. On Mac OS or Linux, you can simply add sudo before the command. Then it's going to ask you for your password and it's gonna rerun it with admin privileges.
1:53:14So let's press enter and the process started. It's building it out.
1:53:20Now let's debug this further. We get the same response we've gotten before. What could this be?
1:53:27Port is already allocated. Oh, yeah. We forgot to delete or close our container that we used for previous React application.
1:53:37So now we know the easy way to do it. We simply go here, we select it, and we can stop it or delete it. Once it is stopped, we can go back and then simply rerun the command.
1:53:51I wanna lead you through all of the commands together even with the failed ones just so you can get the feel of how you would debug or reapproach specific aspects once you encounter errors. That's what being a real developer is, getting stuck, resolving the obstacle, and getting things done.
1:54:09And finally, let's run the command. It's running it and if we go to localhost five one seven three, ah, the same thing as before.
1:54:19Any guesses? The answer is that we once again forgot to add the dash dash host to our Vite dev script right here. So if we add it, stop our application from running by pressing control c, This is going to gracefully stop it.
1:54:37The cool thing about Docker Compose is that it's also stopping the container that it spun up and now that we have canceled our action, we can try to rerun it with sudo docker compose up, but this time with host included and press enter.
1:54:53It's going to rebuild it and if we open it up, now it works. By now, you should have a solid understanding of how to containerize applications within Docker.
1:55:05Of course, you can take this further by experimenting with Dockerizing projects like MERN or Next. Js apps. And if you wanna deepen your Docker skills and see how it applies across different types of projects, check out my full Docker course on YouTube.
1:55:20It's completely free, and the link is down in the description. And if everything makes sense so far, trust me, you're on the right track with DevOps. You will soon be applying this knowledge to containerize production ready applications and connect them with CICD pipelines.
1:55:35I'll cover all of that here. So let's just keep going. In the previous lesson, you learned how to build and run containers with Docker.
1:55:45You saw firsthand how containers package your applications into neat portable boxes that can run anywhere. That's already super powerful, but everything feels amazing until we start getting more users. Suddenly, there are too many requests for a single container to handle.
1:56:06That container has a ceiling, and if it dies, your app dies with it.
1:56:12That's where Kubernetes comes in. Kubernetes is a container orchestration platform. Its job?
1:56:20To schedule, scale, self heal, and load balance containers across machines so your app stays up?
1:56:28So one container isn't enough? Well, a single process handles only a limited number of concurrent requests before CPU and memory become bottlenecks. Sure.
1:56:40You can tune and scale vertically, but there's always gonna be a ceiling and a single point of failure. And you can't bet on one thing for life.
1:56:50If it goes down, your application goes down, and so do you. So you need replicas and an automated way to place and manage them.
1:56:59That is Kubernetes. Often abbreviated as k eight s with the eight representing the eight letters between k and s is an open source container orchestration platform.
1:57:11At its core, Kubernetes helps you run your app across multiple nodes, scale replicas up and down based on demand, restart unhealthy containers automatically, and distribute traffic across replicas, all while rolling out updates without downtime.
1:57:28Docker gives you containers. Kubernetes decides how, where, and when they run.
1:57:34You can think of Kubernetes as the operating system for your container. Without it, you'd be manually starting and stopping containers, keeping track of IP addresses, restarting crashed apps, and scaling things up or down by hand.
1:57:49But to really understand Kubernetes, let's break it down into its building blocks. First, we have the cluster.
1:57:57A cluster is a group of machines, either physical or virtual, that work together as one single system.
1:58:05In Kubernetes, a cluster is made up of a control plane, which decides, schedules, reconciles, and monitors health.
1:58:15And then we have worker nodes, physical or virtual machines where your containers actually run. Each worker node runs the kubelet, an agent that communicates with the control plane and a container runtime like Docker.
1:58:30There is also something known as a cube proxy that handles networking and routing inside the cluster for every node. So when you tell Kubernetes, run three copies of my Node.
1:58:42J s app, the control plane decides where these containers go, and the worker nodes actually run them. Next up, we have pods. In Kubernetes, you don't run containers directly.
1:58:55Instead, containers are wrapped in something called a pod. A pod is the smallest deployable unit in Kubernetes.
1:59:02There's usually one container per pod, and each pod gets its own IP address. So when you deploy your app, Kubernetes runs it inside a pod. You never interact with containers directly in Kubernetes.
1:59:16You only interact with pods, and you can even run multiple pods by specifying something known as a replica set. A replica set ensures a specified number of pods are always running.
1:59:29If you say, I want three replicas, Kubernetes makes sure three pods are running at all times. And if one pod dies, Kubernetes spins up a new one automatically.
1:59:41This is where scaling comes in. You don't manually start containers. You just declare how many replicas you want, and then comes the deployment.
1:59:51Deployment is the higher level object that manages replica sets. It allows you to define updates to your application.
1:59:59Kubernetes can do a rolling update, gradually replace old pods with new ones so your users never see downtime. It handles all of these and ensures reality always matches the desired state.
2:00:11If one pod crashes, it creates a new one. So instead of saying, go ahead and run these containers, you say, here's my app.
2:00:20Here's the image. Here's how many replicas I need. Manage it for me.
2:00:25But there's one issue. Pods are temporary. They come and go, and each time they get a new IP.
2:00:33So how do users or other pods connect to them? They connect using something called a service. A service is a stable endpoint, which is a permanent IP or DNS name that automatically routes traffic to the available pods behind it, and it also load balances requests among multiple replicas.
2:00:53Think of a service as the reception desk. You don't care which employee helps you as long as someone does, And apps often need configuration and credentials, so that's why we have ConfigMaps and secrets.
2:01:08ConfigMaps store configuration data, for example, like a database URL, and secrets store sensitive data, like passwords or API keys. Kubernetes injects these into pods securely without baking them into your Docker image.
2:01:23All of this sounds good. Right? But how do the external users access this?
2:01:28Welcome, Ingress. Ingress is like a smart router that exposes HTTP and HTTPS routes to the outside world.
2:01:37For example, it can map api.myapp.com to your back end service. And similarly to Docker, Kubernetes also has volumes.
2:01:46Since containers are ephemeral, meaning data is lost if restarted, Kubernetes provides volumes for persistent storage.
2:01:55That's what you need to know for now. If you want a separate advanced deep dive on Kubernetes, let me know down in the comments, and I'll record one for you.
2:02:04And, yeah, I think this goes without saying, but you should never do anything directly on production. Before touching production clusters, you can create local clusters that allow you to experiment safely. They simulate a full Kubernetes environment without risking your production apps or cloud costs.
2:02:21Minikube is the most popular choice. It's lightweight and runs a single node cluster inside a virtual machine or container. When I say Minikube runs a single node cluster, I mean that that cluster has only one node that acts as both the control plane and worker node.
2:02:39And this is different from production where clusters are usually multi node, which means that control plane nodes manage the cluster like the API server or the scheduler, and worker nodes run your application workloads containing pods and containers.
2:02:54So with a single node cluster, both roles live on the same machine, which is perfect for development and testing.
2:03:01There are also some alternatives like KIND, Kubernetes in Docker, which runs clusters inside Docker containers. It's great for CICD pipelines and automated testing. And k three s, a lightweight Kubernetes distribution good for Internet of things or resource constrained machines.
2:03:18But I still recommend Minikube for learning because it gives you all the Kubernetes components locally, including the API server, scheduler, and kubelet so you can see production like behavior. So let's get our hands dirty on creating your very first local cluster and running Kubernetes.
2:03:37Let's dive right into the Kubernetes demo. First things first, we'll create a new repo. So I'll call it Kubernetes demo and create.
2:03:47Now you can copy this URL so we can clone the repo within our IDE. I'll do it using WebStorm, so I simply need to provide the URL right here and click clone. If you're using a terminal, you can just say git clone and then paste the URL.
2:04:02Once you're within it, we can run n p m init dash y to initialize a new node application. It'll start with only package JSON.
2:04:11So while we're here, let's also install Express by running n p m install express. Now after that, we'll have to install a CLI for running Kubernetes. And if you head over to Kubernetes documentation, head over to tasks and then install tools, you'll see the kubectl installation on Linux, macOS, or Windows.
2:04:33So just proceed with your operating system. I am on macOS Apple silicon, so I'll simply copy this curl command, head back within our console, and type this.
2:04:43It'll download it and set it up, but, of course, the setup for Windows is a bit different. So pause this video right here and install kubectl. Once you've installed it, you can run kubectl version dash dash client, and you'll be able to see a version right here.
2:04:59After that, you'll also need to install minikube, which is local Kubernetes, focusing on making it easy to learn and develop for Kubernetes.
2:05:08And here, you can choose your operating system, the architecture, and just copy the installation command. Once again, I will paste it for my device.
2:05:15You can clone it for yours. Let's wait until it gets installed. It might ask you the password as well, so just type it in and press enter, and you'll be done.
2:05:25Once it is done, you can run Minikube version to see whether it has been installed properly. If you see something like this, we're good. With Minikube and KubeCTL installed, we are ready to create our index dot j s file, the starting point of our Express application.
2:05:42So just go ahead and create a new file called index dot j s, And within it, you can import Express from Express. You can initialize a new app and define a port.
2:05:55It can be 3,000, eighty eighty, 5,000, or anything.
2:06:00After that, you can create a new empty route that's gonna be a forward slash home route. You can open up a request and a response for this route, and then you can send some kind of an output, like a message of hello world.
2:06:14Of course, to be able to reach this endpoint, we need to turn the server on and make it listen on a specific port. And we can also give it a console log saying something like example app listening on port, and then we define the port right here.
2:06:29Now while we're here, we can also provide a bit more information. So when somebody tries to make get requests to our API, alongside passing the message of hello world, instead of it actually, we'll say something like, hello from a container because we'll be running this within a cube container.
2:06:48We'll also define a service, which will be hello dash node.
2:06:54Then I will also give it a pod, which will be process dot ENV dot pod underscore name, or we can make it unknown if we don't have a pod within our environment variables. And finally, we can define the time.
2:07:09This is gonna be a new date dot two ISO string, so we return it in a human readable format. Alongside this home route, we can also add some basic health endpoints for the probes.
2:07:21We can do that by saying app dot get and then listen on something like ready z where we'll also have a request and a response, and we'll just response with a status of 200 and send a message of ready.
2:07:35If we get this message, that means that we are running. And another very important endpoint that we need to have to make this work is this app dot get health z.
2:07:46This one can send a response of okay. Both of these two endpoints are needed for Kubernetes to know that our app is alive and well. So make sure to have it, and then we can head over to our package JSON.
2:08:00Let's change the type of this application from CommonJS to module so we can use e s six imports and exports. And then let's also add a new dev script that'll run node with a watch flag.
2:08:13So whenever we make any changes, the terminal will be restarted, and we wanna run this index dot j s file. And after adding this dev script, also make sure to add a start script.
2:08:25This one will be even simpler. It'll be just node index j s. In this case, we don't have to add the watch flag because in deployment, we won't be making any changes that we'll have to listen for or watch for.
2:08:38Rather, we'll just run the finished server. Once you do that, we wanna start doing the Docker setup. Since we've already watched a crash course on Docker and since later on in the build and deploy part, we will dockerize that application, for now, I'll provide you with the files needed to make it work.
2:08:55So first things first, we need a Docker file, which you can create by just creating a Docker file. And then within it, within the video kit down below, you can copy and paste the Kubernetes demo Dockerfile, or feel free to pause this video and type it out.
2:09:10We're first defining a base image of an operating system that we wanna run and setting a working directory. Then we're installing all the dependencies separately for caching, copying the app source, and then running the app as a non root user.
2:09:25After that, we will also need a Docker Compose file. So create a new file called Docker dash Compose dot YAML.
2:09:35And once again, I will provide this over within the video kit down below, or feel free to write it by hand, but make sure to use the two spaces to do the indentation and now the tabs.
2:09:46Here, we have the services where we're defining the API service. And within it, we have a build where we wanna build this Dockerfile with a specific container image running on port 3,000 with a node ENV of production.
2:10:01We give it access to different volumes and a command of NPM run dev. I will also define a dot git ignore so that it knows what not to push over to GitHub.
2:10:12It's gonna be node modules, of course. And we also wanna do another one, which will be dot Docker ignore, which will include node modules, MPM dash debug dot log, so all the log files, Dockerfile dot Docker ignore, dot git, and dot git ignore.
2:10:36We don't need those within Docker. And now we are ready to create a new Docker image and run a Docker container in the foreground, which means in an attached mode. You can use it this way when you've changed your Dockerfile or code and want to rebuild it and run it in one go.
2:10:52Basically, this is your go to command in development mode. So open up your terminal and simply type docker compose up dash dash build and press enter.
2:11:06And if you run it, you'll say that it says no configuration file provided, but we have our docker compose here. Or wait.
2:11:13It is Docker Compons. Rather, we wanna make it Compose. So if we fix this snipe, which you most likely didn't have, and we rerun this Docker Compose up build command, you'll see that it'll start building, but it's having trouble finding the Dockerfile.
2:11:31And it looks like I misspelled that one too. It's the Dockerfiler. So let's go ahead and rename it to Dockerfile.
2:11:39Hopefully, you had both of these. Right? And now if I rerun the Docker Compose up build command and there we go.
2:11:47Our Kubernetes demo is now running on port 3,000. If you head over in your browser and head over to localhost three thousand, you'll be able to see a JSON output that we're sending over from our application. Now we can publish this Docker image so we can refer to it when deploying Kubernetes clusters.
2:12:05To publish our Docker image, you can head over to the Docker Hub. Just Google for it, and you'll be able to find it. Then if you head over to your profile, you'll be able to find your username.
2:12:16So just copy it. Once you get your username, you can head over to your terminal. I'll open up a new one because this one is running the server.
2:12:25So now we can use this username and the repo name to add a tag to it by saying docker tag Kubernetes demo API, or maybe it's something different for you, at latest.
2:12:38Then put your username. For me, it's JS Mastery Pro forward slash Kubernetes demo API at latest, and press enter.
2:12:48This will apply a tag. Once the tag is applied, you can then run docker push JS Mastery Pro or your name in this case forward slash the name of the repo or the container at latest and press enter.
2:13:03This will push this image to Docker Hub. As soon as this is done, you'll be able to head back to your Docker Hub. And under repositories, you'll be able to see your first repo.
2:13:12So just reload, and there we go. It's pushed right here.
2:13:16Okay. Dockerization is done. But now is the time to make our Docker image horizontally scalable so that we don't just depend on the resources of a single container.
2:13:27This means that we can actually start working on Kubernetes. First, I'll create a new directory called k eight s, which is an abbreviation for Kubernetes. And within it, I will create a new file called deployment dot YAML.
2:13:44Now Kubernetes is declarative, which means that you describe what you want, not how to do it.
2:13:50You do this using YAML files. Remember those? So within this YAML file, we can declare absolutely everything.
2:13:58From how many copies of the app we want, called replicas, to which Docker image we wanna use, and which ports to listen on. Everything can be done right here.
2:14:08Now I'll share this full deployment with you in the video kit down below, so simply copy it and paste it right here, and we can go through it together. First, we define the API version. Then we add a bit of metadata about the name of our app, and then the most important part is the specification.
2:14:26Here, we define how many replicas of the app you want. In this case, I said two. You add some labels, and then you further define the specification of those containers.
2:14:37You give each container a name and the image to run off of. You also define on which port they'll be running. You can pass some additional environment variables and attach different amounts of resources to these containers, and then you can provide some additional information.
2:14:53This is how you configure Kubernetes. But after that, we also have to provide network access to our pods. So within the k eight s folder, create a new file called service dot YAML.
2:15:07I'll also pass this service file within the video kit down below. So simply paste it here. And the same as before, we have to define our API version, the kind, in this case, it's a service, pass some additional metadata, and select which app we wanna connect with and on which port it is and which type of a protocol we wanna use.
2:15:26And now is the time to deploy it all locally. So first things first, we'll use the CLI that we installed at the beginning of this lesson. It's Minikube.
2:15:34So I'll just say Minikube start. You'll get back the output of what's happening, such as whether the host, KubeLead, and API server are running, and the KubeConfig should be configured.
2:15:46It might take some time to run it properly for the first time. Done. Minikube has now been configured.
2:15:51And once it is done, you can run kube c t l get nodes. In the console, you'll see that Minikube is running a control plane. To check if your cluster is running, you can run kubectl cluster info, and here you can see more info about the control plane.
2:16:09If the cluster is healthy, you'll see something like this where you can see the port where it's running. Without running Minikube, if you directly try to run this kubectl commands, you won't get anything as there is no cluster because Minikube is a tool that sets up a local Kubernetes cluster on a laptop.
2:16:27And without minikube start, there's no cluster running, so kube c t l can't connect anywhere. And finally, we are ready to deploy these files. Service and deployment dot yaml.
2:16:39You can do that in two separate lines by deploying each one individually by saying kubectl apply dash f and then target the path to each one of these files, or you can do it in a shorter single command by running kubectl apply dash f k eight s, which is targeting the entire folder containing both of these files.
2:17:01If you do that, for you, it should say that both got configured. And through this process, the Kubernetes API server will read those YAML files, deployment will create pods via replica set, and the service will set up network routing to the pods, exactly what we learned about not that long ago.
2:17:21So now we can get access to the pod information to check the pod status by running kubectl get pods dash w.
2:17:30You should be able to see two right here. I was doing some additional testing, so I have four. But, essentially, you should see Kubernetes demo API two times because we span up two replicas of our Docker image.
2:17:42You can also get access to the services by running kubectl get services, and you can see those services running right here.
2:17:52And finally, it's time to test out the application. Minikube, thankfully, provides us with a very simple way to access your service, and that is by running minikube service, and then you have to provide the name to your service.
2:18:08And that name was provided for us right here when we ran kubectl get services. In this case, it's referring to the Kubernetes demo API service, so copy whatever you have right here and run minikube service and then the service name.
2:18:27If you do it correctly, you'll immediately see that Kubernetes will open up a service for you in your default browser. Thankfully, it did it for me as well. I'll zoom it out just a bit, and you can see that our server is now live.
2:18:40If I zoom it out even more, I want you to pay attention to one thing, and that is the pod. Kubernetes demo API, and then it has a specific ID.
2:18:52Now if you reload it a couple of times, oh, it looks like I'll have to pretty print it every time, or I can be smart and just installed a JSON viewer pro extension from the Chrome Web Store and add it to my Chrome or Arc, which should give me a more beautiful tree view of the data. So if you now reload it a couple of times, you can see that the last part of the pod ID will change, which essentially means that you're making requests to two different servers.
2:19:19This simple pod change shows that Kubernetes can automatically replace, replicate, and rebalance workloads across the cluster, letting the system scale up or down without intervention.
2:19:33So if one container or pod goes down for whatever reason, the other one is up here and ready to serve your users. This is huge. So what's happening behind the scenes?
2:19:45Well, the API server receives your YAML file. The scheduler assigns pods to nodes. KubeLed starts containers inside pods, and then the service ensures that network is running to these pods.
2:20:00But that's a lot of things that we had to go through. You had to create a Docker image, push it over to Docker hub, start Minikube, do a Kubernetes deployment, get listed pods, get services, and finally test it out.
2:20:13A lot of actions that you would have to repeat again and again. But instead of doing that, you can simply write a bash script and execute it whenever you wanna deploy your app. So create a new file and call it deploy dot sh.
2:20:30Let's write it together. First, you can say set e, which means that we wanna run this script with bash, and we wanna exit it automatically if it fails.
2:20:40Then you wanna define the name of your API. For me, this means that it is a Kubernetes demo API, which is how we called it before.
2:20:51You can also define your username, which in this case is JS Mastery Pro, or for you, it's gonna be your name. And finally, you can provide your Docker image, which is gonna be a combination of your username.
2:21:03So in Bash, you use a dollar sign to use a variable. So forward slash username forward slash name at latest.
2:21:13Then we're ready to build a Docker image. And while running this script, we can also put out some console logs. And in bash, you do it with echo.
2:21:21So you can say something like building docker image dot dot dot, and then you can run docker build dash t and use the dollar sign image variable, which we created above, and then put a dot here to build it right here.
2:21:37Then you wanna push that image over to the Docker Hub. So let's say that using an echo command, something like pushing image to Docker Hub, and you can do that by saying Docker push dollar sign image.
2:21:54Then you wanna apply your Kubernetes deployments and services to YAML configs. So we can also add an echo for that saying something like applying Kubernetes manifests, which are basically just YAML files.
2:22:07So you can run a kube c t l apply dash f and then point it over to k eight s forward slash deployment dot YAML, and you can duplicate it for the service dot YAML file as well.
2:22:21Here, we're applying our Kubernetes deployments and service YAML configs. Finally, we wanna get all the pods, so we can say something along the lines of getting pods dot dot dot and run kube c t l get pods.
2:22:38Then we wanna do a similar thing for getting the services. So we can say getting services by running kube c t l get services. And once we list those services, we can get a service with the name that we have declared above.
2:22:53So we can say something like echo fetching the main service, and you can do it by running kubectl get services, and then you can provide a name of the service.
2:23:04So that's a name dash service like this. And finally, to test it out, we can stop Minikube from running and basically stop everything else that is running.
2:23:15The minikube has to be stopped by running minikube stop. While that is happening, open up Docker desktop and delete the Docker images from desktop. So everything that has to do with Kubernetes demo, the most important one to delete is the one with your username before it.
2:23:31So just go ahead and delete it. Now you can head back over to your terminal and run minikube start to restart this local minikube service that allows us to run local Kubernetes deployments. And then when it starts, we will simply run this single script instead of running all the commands that we previously ran.
2:23:51There we go. It is done. And now just run NPM run deploy.
2:23:57Oh, but let's make sure to add this deploy script to the package JSON by saying deploy. And to run it, you can say s h deploy dot s h. So now if you run this command, you'll see that one by one, it'll say first building the Docker image, then pushing images to Docker Hub, then getting pods, applying Kubernetes manifests, and finally getting the services and listing the last service.
2:24:25And this basically tells you everything is ready to run this service. So you can copy the service name from here. That's Kubernetes demo API service.
2:24:36And run minikube service and then paste the name of the service.
2:24:43If you press enter, you'll see that it'll be running on this port, and you can see the message from a new deployed app on your computer. The pod changes every now and then, and your app is running in two containers. And you never know from which container your request is gonna be executed.
2:24:58Kubernetes will handle all of it for you. And if you wanna scale the app further, just head over to your deployment YAML and change the number of replicas. And you can immediately scale the app within seconds by rerunning the deployment script.
2:25:11I know it might feel overwhelming right now, but with consistent practice, it'll all start to click. The best part is you can keep repeating and testing all these commands in Minikube without breaking anything. Once you feel confident you have mastered the basics, that's when you can step up and move to the cloud.
2:25:31I've already covered a lot about Kubernetes in the context of DevOps here, But if you'd like me to create a dedicated deep dive video focusing solely on Kubernetes, drop a comment down below, and I'll make it happen for you soon. And if you're looking for a complete resource that takes you from start to finish with plenty of real examples, deeper foundational knowledge, and a true understanding of how everything works, then my ultimate back end course is made for you.
2:25:57YouTube videos will give you a solid surface level foundation, But with our courses, you'll develop the mindset of a senior developer.
2:26:06So click the link down in the description, and I'll see you inside. And if the course is not out yet and you really wanna dive deeper into Kubernetes, you can check out our Kubernetes reference guide and an ebook.
2:26:18It's a part of this new YouTube membership thing that I'm doing, so if you wanna support the channel, that's a very easy way to do it. Anyways, amazing job on learning Kubernetes in this part of the course.
2:26:30But now we move forward. Great job.
2:26:36So far, you've seen how Docker makes apps portable and how Kubernetes makes them scalable and resilient. But here's the real question. Where do these clusters actually run?
2:26:49On servers, of course. And those servers could be anything from physical machines in a data center to virtual machines in AWS, GCP, or Azure.
2:27:00Either way, before your first deployment, someone has to spin up compute instances, configure networking, VPCs, subnets, firewalls, and load balancers, set up storage, volumes, databases, and backups, and install runtimes and dependencies.
2:27:16Traditionally, all this was manual work. Click through dashboards, SSH into servers, run ad hoc scripts.
2:27:25That's slow, error prone, and impossible to scale. This is where infrastructure as code or ISC for short changes everything.
2:27:36ISC means managing infrastructure, servers, networks, and databases with code instead of manual setup. Think of it like writing blueprints for your entire infrastructure.
2:27:48So instead of saying, hey, ops team, can you create these three servers and hook them up with a load balancer? You just write code that looks something like this. Resource AWS instance web, AMI ID, instance type, and the count.
2:28:04Run this, and you get exactly those three servers. Change count to five, and two more are provisioned. Your infrastructure immediately becomes version control because you can track changes in git, testable so you can verify configs before deployment, reproducible because you can rebuild those environments instantly, and shareable as you can onboard new engineers fast.
2:28:27So IAC is all about consistency, speed, scalability, and collaboration.
2:28:34And you don't need to master every provider service like AWS CloudFormation, Azure Resource Manager, or so on. The industry prefers cloud agnostic tools that work anywhere.
2:28:47One of those is Terraform by HashiCorp. You simply define infra in HCL and deploy across AWS, Azure, GCP, and more with a single workflow.
2:28:59One script could create a Kubernetes cluster in AWS, a database in Azure, and storage in GCP. There's also Helm, a package manager for Kubernetes that turns messy YAML files into reusable configurable templates.
2:29:15So if you're starting out, simply focus on Terraform as a general purpose ISC for all clouds, Helm for managing Kubernetes deployments, and then cloud specific tools later if you go deep on one platform.
2:29:29This skill set will make you valuable anywhere and keep you out of that vendor lock so Bezos can no longer control what you do.
2:29:38Over the last couple of years, you've built a solid DevOps foundation. You started with what DevOps really is and why it matters, then moved into hands on skills that every DevOps engineer needs.
2:29:52Version control for smooth collaboration, CICD pipelines to automate testing and deployments, Docker to package and run applications consistently, Kubernetes to orchestrate containers, scale apps, and manage clusters, and then ISC to define and deploy infrastructure reliably.
2:30:12With these fundamentals, you now understand how modern software goes from code to production, automated, scalable, and secure.
2:30:23Now it's just practice, building real projects and layering on advanced tools as you go. And speaking of projects, let's put everything together into action.
2:30:34It's time to actually build and deploy a scalable API using all the DevOps practices you've learned so far. That means that this is not the end.
2:30:45It's where the real learning actually begins.
2:30:50Alright. We're finally about to jump into the big part of this video, building and deploying our production ready API.
2:30:58And just a quick reminder before we continue, DevOps is all about doing and not watching. So that means that you gotta have the right stack of tools set up.
2:31:08As I mentioned before, for the database, we'll be using Neon. So go ahead and click the link down in the description and click start for free. For security, we'll be using ArcGET.
2:31:18BAW detection, RAID limiting, email validation, attack protection, you name it, and we get painless security. And then warp, where we'll be running all of our commands, shipping code, and even automating tasks with AI.
2:31:33If you're not using it, you'll constantly feel a step behind. And don't forget, the pro plan is still just $1, which gives you unlimited workflows and faster builds.
2:31:42Once again, the link is in the description. So if you haven't yet set these up, pause for a second, get them done, and then come back. Once you've got all three, you'll be able to follow along seamlessly as we build and deploy the API.
2:31:56So now the first step is to create a new GitHub repo. You can call it acquisitions. We need to create a repo as soon we'll need to implement CICD pipelines.
2:32:06So better to create a repo from the start. You can just click create repository and then clone it locally on your device by copying this link and cloning it locally onto your system.
2:32:17In this case, I'll be using WebStorm, so I can just go ahead and click clone repository, paste the URL, and just click clone.
2:32:26You can do it normally using Git. Once you're there, we need to initialize a new Node. Js project.
2:32:33So just run l s to make sure you're in the right repo. I'll go ahead and expand my terminal as we'll be spending quite a lot of time within the terminal, but later on, we'll be using warp, and I'll run n p m in it. And you can also add the dash y to just press enter to all the default options.
2:32:49Just like that, you'll be able to see a new package JSON, which is the root of our application. Then you can install Express by running NPM install express as well as dot ENV, which we'll use for our environment variables.
2:33:04Once that is done, you'll see that you'll get a package JSON, and I'm currently hiding the node modules folder as I don't typically wanna go into it, but it is important that we exclude it from Git so it doesn't get pushed over to GitHub. To do that, you can add a new file called dot git ignore, and then you can just say node underscore modules to exclude it from being pushed over to GitHub.
2:33:30After that, you wanna head over into the package JSON and modify this application to use the e s six import system, which you can do by adding an additional type property or key and setting the value to module.
2:33:45This refers to e s six plus modules. Oh, look. Type is already here below.
2:33:51So we just wanna switch it over from common JS to module. Next, you wanna create a new file in the root of the directory, and you can call it index JS.
2:34:01This will be the starting point of our application. Within it, simply import express from express, initialize a new application by setting it equal to the call of the express library, then set the port equal to either process dot ENV dot port if it exists, or by default, we can make it 3,000.
2:34:22You can also do eighty eighty, 5,000, or any other number. Finally, we wanna make the app listen on that port.
2:34:29And once it starts listening, we wanna simply put a console log out saying listening on port.
2:34:36Now to run this application, we need to add an additional script within a package JSON under scripts.
2:34:44For now, I will remove this test script and instead of it, add a dev script that when ran will run Node dash dash watch index dot j s.
2:34:57Now this dash dash watch flag tells Node. J s to automatically restart your program whenever a file changes in your project directory. Very important.
2:35:08And now if you go ahead and run NPM run dev, you'll see that it'll say node watch index j s listening on local host 3,000. Perfect. And you can stop it from running by pressing control c.
2:35:20And instead of just checking out this project locally and calling it a day by having a single file from which all of it is running, I actually wanna teach you how to create a proper production level file and folder structure. So let's start by creating a new folder, which you can call SRC or source.
2:35:41Within the source folder, we wanna create a new app dot j s file. Then right next to the app, still within the source folder, we'll create another file, which is gonna be called index dot j s, and there's gonna be a third one called server j s.
2:35:59All three of these files will have their own purpose. The app file is all about setting up that express application with the right middleware, whereas the server JS is all about running that server, implementing some logging, and everything else to make sure that the server is running properly.
2:36:17And then index is just like a starting point. Now let's create a couple of other folders within the source folder. The first one I'll create will be called config.
2:36:26This is a folder for all different kinds of configurations. Then we have controllers, which is also within the source folder.
2:36:34As a matter of fact, every new folder we create will be within the source folder because the source is basically our entire application. Now when speaking of controllers, that has a lot to do with the model view controller paradigm in developing back end applications.
2:36:49That's something we'll dive much deeper into within our back end course. Alongside controller, we also have the middleware.
2:36:56So create a new folder called middleware. Middleware are functions that are run before or after some other functions that our app does. Maybe logging functions, so whenever a request is made, you can see what happened.
2:37:09Or maybe authentication or verification actions to make sure that when somebody tries to perform a specific API action, the middleware checks whether that user has the permissions to do so.
2:37:20After that, another folder that we'll create will be a models folder, defining how our database schemas and models look like. Next, we'll also have a routes folder defining our API routes.
2:37:34And you can see how in my IDE, each one of these folders has their own icon because these folder names are actually a convention which a lot of developers use. After routes, we can also create services.
2:37:47After services, we can create utils for utility functions. That's also within the source folder.
2:37:55And finally, we'll have validations for all different kinds of validations within our application. Right now, these are just different empty folders and meaningless names.
2:38:04But as soon as we dive deeper into developing the application and we start putting actual files within them, it'll all start making so much more sense. Now you wanna move the current Index. Js express app setup that is within this outside Index.
2:38:20Into the app JS. So simply copy it and move it over to app JS. This is where we're setting the express application.
2:38:28And instead of defining the app listen and the port, we can create a new endpoint by saying app dot get forward slash.
2:38:36So this is the home route. We get a request and a response, and we can respond something once the user triggers this endpoint or reaches it.
2:38:47Res dot status of 200 dot send hello from acquisitions API or just acquisitions is enough.
2:38:58And then we can finally export default this app.
2:39:03Now we can once again copy this Index. Js and paste it over into server because here, we won't have the actual app dot get route, but here, we'll actually be listening over to the server.
2:39:16So we don't have to recreate the app from Express. Rather, we just have to import the app from dot slash app. So now we can see how we're connecting it together.
2:39:27Now if you head over to the index JS within the source, you can now import the dot ENV forward slash config to make sure that we can properly read environment variables, and you can also import the dot slash server JS.
2:39:43So now if you head back over to your package dot JSON, you can modify your dev script to run source index JS instead of just index JS, and then we can delete this index JS from the root because we no longer need it.
2:39:58So now everything we need is within the source folder. Primarily, we're creating the express application right here within the app, and then we're also listening to the server within the server file.
2:40:11What you can do is maybe even say h t t p colon forward slash forward slash and then add the port and delete these three dots. Oh, and before we run it, make sure that at the end of all the imports, you add the dot j s extension or whatever other extension we have.
2:40:29In React or Next. J s environments, it's no longer necessary. But here, with e s six modules in Node, you have to specify the extension.
2:40:37So once you do this, you can go ahead and open up the terminal and run NPM run dev. And now it'll say listening on h t p colon forward slash forward slash. Oh, and looks like I forgot to add local host.
2:40:51But now if I add it, you can see that it auto restarts because of the watch flag. And now you can just click on this link within your terminal, and it'll open it up within your browser saying hello from acquisitions.
2:41:04This means that we have now created the base file and folder structure of our running Express application. Congrats. Let's not forget to push it over to GitHub.
2:41:14And just to stick with proper programming habits, let's go ahead and commit this no matter how small of a commit it is. The smaller, the better.
2:41:22So I will rename this active terminal to app because it's gonna be running our app, and I'll create another terminal right here within which we can run additional commands. So I'll just say git add dot git commit dash m initial commit and git push.
2:41:42Immediately, all the changes are pushed.
2:41:46In this lesson, we'll implement a step that is very easy to skip, and a lot of people in YouTube teaching these courses simply skip over it. And that is setting up and installing ESLint and Prettier.
2:42:00It is always useful to have it, but even more so when you combine it with CICD pipelines so it always properly formats your code. First things first, we gotta install all the necessary dependencies by running NPM install, ESLint, add ESLint forward slash j s, Prettier, ESLint dash config dash Prettier to make them work together, ESLint dash plugin dash Prettier, and dash capital d, which stands for development.
2:42:31Only install these as development dependencies. When you install these packages, create a new file called ESLint dot config dot j s.
2:42:42And within it, we can paste our new ESLint config. Now this is the config that I was building over the last couple of years. But in simple terms, it just extends the recommended JavaScript config, and then it adds some additional rules.
2:42:56You can find it and copy and paste it from the video kit down below. Once you add it, we can also add another file for Prettier. Oh, and make sure that it's not within the source folder, but within the root of the application.
2:43:10The Prettier file is also within the root, and it's called dot Prettier r c. And within here, we can form an object with some settings such as whether you need or don't need semicolons, the trailing commas, that's the comma at the end where you don't actually need it, but you can have it, whether you wanna use single quotes or double quotes, and so on.
2:43:30Feel free to pause the screen right here and type these out. Or you know what? I'll also leave it within the video kit down below.
2:43:37Finally, we can add an additional file called dot prettier ignore.
2:43:43And within here, you can paste the files and folders that you want prettier to ignore. It's gonna be node modules, coverage, logs, Drizzle, logs, and package log JSON. Once you do that, head over into your source app dot j s.
2:43:58And right here, you should be able to see red squiggly lines telling you that we have some issues with ESLint, such as a missing semicolon, inconsistent spacing, or a missing semicolon here as well.
2:44:11This means that linting is working. Now to lint across all of our pages, head over within our package dot JSON, and then let's add a new script for linting. We'll use this script later on in our CICD pipelines to ensure our code is formatted properly.
2:44:28So just add a new lint script and make it simply call ESLint dot. Then you can also add a Lint fix, which will run ESLint dot dash dash fix.
2:44:43You can also add format, which we'll use with Prettier. So it's Prettier dash dash right dot.
2:44:51And finally, we'll have format check, and this will be equal to Prettier dash dash check dot.
2:44:59Now if you open up your app dot j s and open up your terminal at the same time and type n p m run lint, you'll see that we'll have 55 errors in this very small application, mostly due to indentation errors and missing semicolons.
2:45:16Then to automatically fix them, simply run n p m run lint fix. And as you can see, within a single terminal command, all of these issues were fixed automatically across all files.
2:45:29And to also enforce prettier formatting, you can also run NPM run format. In this case, it was already good.
2:45:38Or to check if formatting has been done properly, you can run NPM run format check. Perfect. All matched files use per year code style.
2:45:49Wonderful. This means that now we have all of the necessary scripts that we'll be able to later on run from our CICD pipelines. So what I'll do is run git add dot git commit dash m, implement ESLint and Prettier, and git push.
2:46:08Perfect.
2:46:11If you've been following along, you most likely already have an account on Neon. But if not, let's create it right now by clicking the link down in the description and starting for free or simply logging in. Once you're in, create a new project.
2:46:24You can call it j s m underscore acquisitions, and you can choose the region that is closest to you, and click create.
2:46:32Once you're in, you can click connect and then copy this connection string. Then within your application, create a new dot ENV file in the root of your application, and let's add a comment for server configuration.
2:46:49So here, we can paste all of the environment variables that have something to do with the server, such as the port by default set to 3,000, node environment by default set to development, and log level, which we can set to info.
2:47:06Then we can also do a database configuration. And here, you can paste the database URL and make it equal to the string that you just copied over from Neon.
2:47:18But make sure to remove this p s q l at the start if you have it and also the ending string sign right here. Then we can also update our dot git ignore to ignore all types of different ENV files by saying dot ENV dot and then asterisk. And then it is always a good idea to also, alongside dot ENV, create a new dot ENV dot example file.
2:47:43This serves the purpose of telling people which variables they need, but it won't actually include the sensitive information. Now let's install Neon by running n p m install at Neon database forward slash serverless Drizzle dash ORM.
2:48:01So we're installing both Neon as well as Drizzle to keep our database queries type safe. And we also wanna add one dev dependency, which will be for Drizzle kit.
2:48:11So as soon as this is installed, simply run n p m install dash d Drizzle kit.
2:48:18And then we are ready to configure our Drizzle config by creating a new file. It's gonna be in the root of our application, and you can call it drizzle dot config dot j s.
2:48:32Start by importing dot e n v forward slash config at the top so we can actually refer to our environment variables, And then you'll need to export the configuration for Drizzle, which will include a schema, which is actually a path to all of the models.
2:48:49So that's gonna be dot slash source forward slash models forward slash asterisk dot j s.
2:48:56This means we will store the schemas right here. Then we can choose the output, which is just gonna be dot slash drizzle. We can also do a dialect of SQL that we're using.
2:49:07In this case, it's gonna be PostgresQL, and we need to pass the DB credentials. That's gonna be an object where the URL is process .en v.
2:49:18Database URL. And now if you run that ESLint fix command or just run ESLint fix, it'll fix all the inconsistencies within this file.
2:49:29But, again, we don't really have to worry about it because later on, we'll make sure that linting is a part of our CICD pipeline. Now let's go ahead and set up the database by heading over into source, config, and create a new file within the config folder and call it database dot j s.
2:49:47Within it, you can also import dot e n v forward slash config. You can also import Neon as well as Neon config coming from at Neon database forward slash serverless as well as import Drizzle coming from Drizzle ORM.
2:50:05And in this case, we can use Neon HTTP. Then we wanna initialize the Neon client by saying const SQL is equal to Neon to which you need to pass the database URL.
2:50:18So process.env.database URL, and you'll need to initialize the Drizzle RM by saying d b is equal to Drizzle to which you pass this SQL variable.
2:50:29And finally, you export both the database and the SQL. For now, since the Neon config is not used, we can remove it and bring it back later on if needed. Now we can head back over to our package dot JSON and update our scripts to add additional Drizzle scripts.
2:50:48We can add them by saying d b generate. That'll be Drizzle dash kit generate, and I will duplicate it two more times.
2:50:58Then we will have d b migrate, which will run drizzle kit migrate, and finally, d b studio, which will run drizzle kit studio.
2:51:07And now I can show you how we can create a first model in our application. That model is gonna be for users. So head over into source, models, and create a new file called user dot model dot j s.
2:51:23Within it, you can export const users and make it equal to p g table, which you need to import from Drisula RM p g core.
2:51:33As the first variable, you're gonna pass the users. That's the name of the table. And finally, then you have to pass the columns.
2:51:41In this case, we can say that a user's table needs to have an ID, which is gonna be a serial ID, and it'll act as the primary key of this table.
2:51:51It'll also have a name that'll be a VARCHAR, which we can import from the same Drizzle RM p g core of a name and a max length of two fifty five, and we need to make sure that it is not null.
2:52:08Now I will duplicate this one, two, three more times. For the second one, we're gonna be talking about the email. So we can say that email is of a VARCHAR email with a length of two fifty five, not null, and this one also has to be unique.
2:52:24After that, we're gonna have a password. So that's gonna be a password of a VARCHAR password with a length of two fifty five, not null, and finally, a role.
2:52:34So I'll say role, VARCHAR role with a length of 50, not null, and by default, we can set the role to just a regular user.
2:52:44Finally, we can set a created at field, which is gonna be a time stamp coming from p g core, and it'll default to now.
2:52:55So dot default now dot not null.
2:53:00And I will duplicate it so we can also store the updated at field. And let's not forget to import this serial at the top. Perfect.
2:53:09So now we have created a users table. And the way it works with Postgres databases and Drizzle is that now we need to generate SQL schemas using Drizzle by opening up a terminal and running n p m run d b generate.
2:53:27Once you run that, we will have gotten a new SQL migration file right here under Drizzle. And here, you can see that we just created a new user stable.
2:53:38After that, we'll need to migrate or push the changes over to NeonDB by running NPM run DB migrate. If you do that, you'll be able to see a warning, but everything should have went through successfully.
2:53:50So now if you head back over to your Neon dashboard and go over to tables, you should be able to see a new users table. Perfect.
2:53:58This means that we have successfully set up a Neon Postgres database with Drizzle RM. So let's go ahead and commit it by saying git add dot git commit dash m setup Neon Postgres with Drizzle and git push.
2:54:17Perfect.
2:54:19In this lesson, we'll set up logging and middleware. For that, we'll use a super popular logging library that has over 24,000 stars on GitHub, and it can log just about anything, info, errors, debugging, and more.
2:54:34So let's just install it by opening up our terminal and running n p m I Winston. Then we can set it up by heading over to source config.
2:54:45And within config, you can create a new file called logger dot j s. Now if you head back over to this GitHub repo, you'll see some documentation about how to set it up.
2:54:55So you can just copy this usage part where they guide you how to create your own logger. If you paste it, you'll see that we first import it. In this case, they're using the old require.
2:55:06So what I'll do is I'll just change it over to import Winston from Winston. There we go. And once that is done, we're basically creating our own logger by calling winston.create logger, and then we pass over some info, such as the info level.
2:55:25In this case, if we pass something different from our environment variables, such as process dot e n v dot log level, then we'll use that.
2:55:35Else, we'll just use info. But if you remember, this log level for now is just info anyway. Then for the format, instead of using the default JSON one, we'll actually combine a couple of things.
2:55:47So say dot combine, and then we can pass over all the different formats. I will use the time stamp format so we know when the log happened.
2:55:57We'll combine it with the errors by saying winston.format.errors. And I wanna see the whole stack of the error, so I'll set it to true. And then only then I wanna see the whole JSON right here.
2:56:11Then we have the default meta, which is gonna be the name of our application, So I can set it to acquisitions API.
2:56:19And then we have transports where you define the importance level of error or higher to error. Log.
2:56:26So in this case, we have new winston dot transports file with file name error dot log.
2:56:34So here, decide where should the Winston create a new file for the error logs. I'll put them over to logs error dot log with a level of error.
2:56:45And then for the other ones, we can just pass them over to logs forward slash combined dot log. Then if we're not in production, then log to the console with the format info level info message JSON stringified.
2:56:59So in this case, we're simply saying if the node environment is not production, then log something to the console.
2:57:07In this case, I'll also modify the format by saying Winston dot format dot combine. I wanna colorize it we can see it in colors as well as I wanna keep it simple.
2:57:17So I'll say dot format dot simple. I found these two properties to work the best when logging. And finally, I wanna export that logger by running export default logger.
2:57:30So now if you head over into source and then app dot j s, we can now add that logger to whenever a user makes a request to forward slash. So I'll say logger.
2:57:42Make sure to import it from dot slash config logger js.info, and I'll say hello from acquisition.
2:57:51So now we'll be able to see it not just in the browser or the return of our API, but also from the logs. But before we go ahead and test it out, check out this top part right here where it says dot slash config forward slash logger j s.
2:58:07There's nothing necessarily wrong with that. That is the file path of where we're importing this logger object from, but you can easily make a mistake here. Maybe if you forget a dot or a forward slash or if you mess up the path.
2:58:21So this is called a relative import system, but I would much rather prefer to use the absolute import system. So let me show you how.
2:58:30You know how in Next. Js apps, you just have something like import something from at forward slash test.
2:58:37Right? How you're importing different packages as well. They're just there.
2:58:41You don't have to search throughout the entire relative path. Oh, and if you're importing logger from somewhere else, you would have to maybe go a couple of levels deep to get into that file.
2:58:50So it's prone to errors. But imagine that you could just call the logger from anywhere by saying something like add logger. This would be pretty cool.
2:58:59Right? So let me show you how to set it up. It all has to do by heading over into package dot JSON.
2:59:06Yep. Again, you can see how DevOps has a lot to do with setting up the scripts, but this time, it's not gonna be the scripts. It's gonna be the imports.
2:59:15So right above scripts, you can create imports, which is gonna be an object, And there, you can define hash config forward slash everything.
2:59:26So everything within the config folder, we can automatically point to that path dot slash source config everything.
2:59:35And now I will duplicate this for every single folder that I have. One, two, three, four, five, six, seven. I don't know how many there are, but we can do the same thing for every single one.
2:59:46So I'll repeat it over for controllers, then we have middleware, then we have the models, there's the routes, services, utils.
2:59:57And look at that. I duplicated it the exact number of times ending with validations. So now if you go back to AppJS and you wanna use the absolute import system to import the logger, now you would go ahead and say something like hash config forward slash logger j s.
3:00:14Now I'll show you how Winston does all of this logging very soon. But just before we do that, let's also install something known as a Helmet. See, Helmet j s helps secure express apps with various HTTP headers.
3:00:29It also has over 10 k stars and is a widely recognized package. So we first have to install it by running n p m I Helmet, and it is a very lightweight package, so it'll get installed within a second.
3:00:42Then we can copy its usage right here, head over within the AppJS, and paste it. You see that we have some duplicates.
3:00:50We're already creating the app, and we're just importing helmet from helmet. And then right below we initialize the app, we say app.use helmet.
3:01:01In this case, helmet would be considered a middleware. And alongside helmet, we'll also set up Morgan. It's a logging middleware that'll show you details like the method, URL, status code, or response time whenever somebody makes a request to our API.
3:01:16Basically, we use it to monitor traffic and debug requests easily, especially in development. So let's scroll down to its usage. First, you have to install it by running NPM install Morgan, and then you'll have to use it.
3:01:30Using it is super simple. You do almost the same exact thing as before by first importing Morgan coming from Morgan.
3:01:38Then in this case, we'll also allow our application to pass JSON objects through its requests by saying app dot use express.json and also app.
3:01:50Use express dot URL encoded extended to true.
3:01:56This is a built in middleware function in Express, and it allows you to parse incoming requests with URL encoded payloads based on body parser. So, finally, we can now also use the Morgan by saying app dot use Morgan combined, so both in dev and production, stream.
3:02:16And here, we can define what it'll actually do. So it'll write, and then you can define a callback function.
3:02:23When it gets a message, it will simply return logger dot info, and then it'll pass the message dot trim.
3:02:33So in this case, we're actually combining both our logging library through Winston and Morgan by passing over Morgan's messages into our logger.
3:02:42And while we're here, let's also set up a couple more very important pieces of middleware. I'll open up the terminal and install them one by one by running n p m install course.
3:02:53See, course lets your back end decide which external domains can make requests to it. Without it, browsers will block calls from different origins, like a React app on localhost 3,000 calling an API on localhost 5,000. Then we also need cookie parser.
3:03:10Cookie parser will read cookies from incoming requests and make them available in Rec dot cookies. It's super useful for handling sessions, authentication, and storing small bits of user data.
3:03:21And finally, dot express dot JSON, which we used. This one, you don't have to install. It's already built in.
3:03:27But, basically, it parses JSON data in the request body and exposes it to you so you can access it within reg dot body. It's essential for APIs since most clients send data in a JSON format. So let's install them, and let's set them up within our app dot j s.
3:03:45You can already see how the app is growing larger. Right here, after helmet, I will say app dot use and set up course.
3:03:54Make sure to import it at the top by saying import course coming from course. And right here at the end, I'll also say app dot use, and we will get access to the cookie parser.
3:04:09Make sure to also import the cookie parser right here at the top from cookie dash parser like this.
3:04:19Now if you reload your application or just rerun it on localhost 3,000 and just make a request to it by heading over to localhost three thousand, when you close it, you'll be able to immediately see a Winston log within the console, which says hello from acquisitions. That is this part right here, logger info.
3:04:39It's also letting us know which service this is coming from, gives us more information about the date, the operating system, and all the other information.
3:04:48Oh, and also, if you head over into acquisitions logs, the folder we created not that long ago, you can also see the logs created for us by Morgan.
3:04:59All of the HTTP information is stored right here so we can retrieve it whenever needed. This is super important when debugging your servers. Perfect.
3:05:08And with that in mind, you have just successfully set up logging and middleware within your back end application.
3:05:16In this lesson, we'll get started with implementing the authentication. So head over into our source, and let's create our first group of routes.
3:05:27I'll create a new file which will be called auth dot routes dot j s.
3:05:35And within it, you can first import Express coming from Express, and then we can get access to Express's router functionality by seeing router is equal to Express dot router.
3:05:49Router allows you to create routes. You can do it like this. Router dot post, so you're creating a post route at forward slash sign dash up.
3:06:01And then as the second parameter, you provide a handler, which is a function that defines what will happen once this endpoint is reached. So in this case, we can define a new callback function that will be executed Within the block of the code, you write what's gonna happen.
3:06:18But within the first two parameters, you're getting access to the request and the response. The response has the send method that allows you to respond to the user that's trying to access this endpoint.
3:06:31So here, you can tell it something like post forward slash API forward slash auth forward slash sign up response. And now I will duplicate this two more times. For the second one, we will trigger the sign in functionality.
3:06:46So I'll say coming from sign in. And for the third one, I'll do sign out. Perfect.
3:06:54Now for this router to work, we first have to export it by saying export default router, and then we need to use it within the app .js. So now if you head a bit below, right here, we have the app dot get, you can also add app dot use similar to how we use the middleware, but you can also use the routers like this by saying all of the routes within this router will start with forward slash API forward slash auth, and then we expose this entire auth routes, which we can import at the top.
3:07:30So now when somebody goes to forward slash API forward slash auth forward slash sign in, they will hit this sign in route right here.
3:07:39Alongside using this router, let's also add something known as a health check by adding the app dot get, and it's nothing more than just another endpoint, which is gonna be called health. It'll once again have the request and the response.
3:07:54And within it, we'll simply say rest dot status of 200, and we'll send over a JSON object that'll have a status of okay. It'll also have a time stamp of the current date and time.
3:08:07We can even put it to ISO string by saying new date dot to ISO string, so it's in a human readable format. And finally, uptime, which is process dot uptime to define for how long has our server already been up.
3:08:22And alongside the health, since we just exposed this new router on the auth route group, we can also create another endpoint, app dot get, which is not gonna be forward slash, which we have right here above.
3:08:37It'll rather be forward slash API. And if somebody tries to go to forward slash API, we'll say res dot status 200 dot JSON, and we'll send over a message saying something like acquisition API is running.
3:08:52Perfect. So now we can test all of these API routes. So let's actually go ahead and get these routes tested.
3:09:00What you can do is first ensure that the server is running on localhost 3,000 by running n p m run dev. And once it is, you can visit it within the browser and then manually change the URL path to something like forward slash health. But you can only do that for get requests because what does it mean to load a website?
3:09:20Think about it. It means just making a simple GET request over to that endpoint. So if you wanna test GET routes, you can do that with the browser.
3:09:30But as soon as you have a post, put, update, or another type of a route type, then you have to use something known as an HTTP client. There are many different clients out there.
3:09:41There's Postman, Insomnia, but recently, I used the one that I found the simplest, HTTPy.
3:09:47Simply Google HTTPy, go to their web app. Once you're within h t t p, colon forward slash forward slash local host 3,000, and we can test it out.
3:10:00You'll quickly see that we get a DNS error saying to check the URL and try again, or for local networks, download the desktop app.
3:10:09So in this case, let's go ahead and very quickly install h t t p I on our device. It'll try to open it. If you don't have it, just quickly install it.
3:10:17We once again get a DNS error, But this time, if you just say colon 3,000, so we're hitting our local network, you can see that we indeed get back a response.
3:10:28This is a response in an HTML format, hello from acquisitions, because we didn't use res dot JSON to send the response. We just use res dot send.
3:10:38But if you head over to forward slash API, in that case, you'll see that we get back a much nicer JSON format object saying acquisitions API is running. And if you head over to forward slash health, you'll see that we get the timestamp and the uptime.
3:10:55Everything is working perfectly. What this also allows us to do is to make a post request to forward slash 3,000 API auth sign in.
3:11:06And if you click send, you'll see that we reached the post sign in response. So now is our time to get it implemented so we can then test it out using this HTTP client. So heading back over to our IDE, you can open up the terminal and install a new package called JSON Web Token.
3:11:26JSON Web Token or JWT for short is a compact URL safe means of representing claims to be transferred between two parties. In simple words, we use them to ensure that our users are signed in and that they are who they claim to be. The way you can implement JWTs is by heading over to source, and we can get started by creating a utility function that'll make it easier for us to use JWTs across the entire authentication process.
3:11:56So create a new file called JWT dot JS. And within here, you can import JWT from JSON Web Token, and let's set up our JWT secret by saying const JWT secret or underscore secret is equal to process dot ENV dot JWT secret.
3:12:18Or if we don't have anything within our ENVs, we can just have it here by default by saying something like your secret key, please change in production. So it's very important that this is not coming from your local code.
3:12:32It needs to be coming over from environment variables. And you can also define another constant for how long for that JWT to expire.
3:12:42So JWT expires in, and we can set it to one d as in one day.
3:12:48And then we can create this new JWT token, which is basically just an object that has a couple of different methods on it, which we will define. The first method will be called sign, and this method accepts a payload, and it'll try to return a signed JSON web token after it verifies that we are who we claim to be.
3:13:12So we can open up a new try and catch block. In the catch, if there's an error, we can use the logger functionality to log that error saying something like failed to authenticate the token, and then we can render the actual error.
3:13:28And we can also throw that error within the application. In the try, we will actually return a signed JWT with the payload that we passed in by verifying the secret and passing over as options when this JWT expires.
3:13:47A second part of this JWT config besides the sign is the verify. So after we have a signed JWT, we also have to verify it with a special token.
3:13:59I'll also open up a try and catch block. In the catch, we can do the same thing that we did before. We'll say logger.
3:14:07Error, fail to authenticate token, and log the error, and then throw that error so we can catch it. And then in the try, I will return the verified token, so j w t dot verify token and the secret.
3:14:24This will only work if we have access to our apps or JWT's secret. And don't forget that we're already exporting this, so we'll be able to use it very soon.
3:14:35But just before we use it, let's also create helpers for the cookies. We can do that right here within utils and create a new file called cookies dot j s.
3:14:47And following the same fashion, we can export a new object called cookies, which will have a couple of different methods attached to it. The first one will be to get the options. It's a callback function with an automatic return.
3:15:00An automatic return means that we don't just put curly braces here that's opening up a code function block, but rather we wrap it with parentheses, and that means that we're actually returning this object. Here, we can say that it is an HTTP only cookie to make it more secure.
3:15:17Talking about security, we can define the environment. So in this case, it'll be process dot ENV dot node ENV has to be equal to production.
3:15:28If it is, then we are in the secure mode. We will also set the same site origin to strict as well as a max age of this token to be 15 times 60 times 1,000.
3:15:43This is fifteen minutes. So 15 times sixty seconds times 1,000 milliseconds. Let's also set up a couple more methods such as the set.
3:15:52How can we set the cookies? Well, we can set them by first accepting couple of things within parameters. So we'll be accepting the response, the name of a cookie, the value that we wanna set to it, as well as some additional options, which by default will be set to an empty object.
3:16:12And then we can say res dot cookie, pass the name and the value, And finally, we wanna spread dot dot dot cookies dot get options, and then we wanna append additional options to it if we do decide to provide some additional ones.
3:16:28Now we wanna do a similar thing with a clear. When we wanna clear the cookies, we will also be accepting all of these different props. So res, name, and options.
3:16:39By default, these options are equal to an empty object. And then we will say res dot cookies. Instead of calling res dot cookie, we will call res dot clear cookie with a specific name, no value in this case, and we'll still be providing all of the options.
3:16:55Finally, once we have set it or reset it, we wanna be able to access it. So I'll create a get method with a rec and a name of that cookie, and we will simply return rec dot cookies under a specific name.
3:17:09Perfect. Now before we test this out, we now also wanna install an additional package by running n p m install zod.
3:17:17Zod is another one of those very popular back end packages. It's actually a TypeScript first schema validation with static type inference with almost 40,000 stars.
3:17:28We'll use it to define schemas with strongly typed validated results. So let's define how our sign up schema should look like. I'll do it right here within the validations folder by creating a new file called auth dot validation dot j s.
3:17:45Within it, we can import in curly braces z from Zod and then export this schema.
3:17:54Export const sign up schema is equal to z dot object, and here we can define what the schema needs to have.
3:18:02We can start with validating the name field by saying name will be z dot string, and we can trim it. If you want, you can also provide some additional parameters like dot min to define the minimum amount of characters that it should have or dot max, something like two fifty five, and then trim typically comes at the end.
3:18:22Let's also continue doing that for the email by saying email will be of z dot email with a max of two fifty five.
3:18:33We will lowercase it, and we will trim it in case people left some extra characters. I'll also do the password with z dot string dot min of about six, max of about one twenty eight.
3:18:46That's more than enough for our password. We will not lowercase it. Password can contain uppercase characters, and we will not trim it.
3:18:54Let's not mess with passwords. And finally, the role of a user. I'll set that to be equal to ZDotEnum.
3:19:02Enum stands for, you know, some of the options that we can choose, such as either a user or admin. And then finally, we will set it to default to user.
3:19:13Finally, we can also have a sign in schema, which will be very similar. So I'll say export const sign in schema z dot object.
3:19:22It'll have an email of z dot email dot to lowercase dot trim, and it'll have a password.
3:19:31So if somebody's trying to sign in, they're only using their email and password. So ZDotStringDotMin, one character.
3:19:38We basically need a password right here. Perfect. So now we have those schemas which we're exporting so we can use them within our application.
3:19:47And we also have to somehow properly format all of these errors so that they can get sent over to the user. For that, I'll go ahead and create another utility function within utils, and I'll call it format dot j s.
3:20:02Within here, we can export a new function called format validation in case we're getting many errors.
3:20:09So this one will accept all of the errors. And then if there is no errors or no errors dot issues, we will simply return validation failed.
3:20:21But if there are some errors and if it's a type of an array, so say array dot is array, what an interesting way to check whether something is actually an array, then we will map over all of those errors by saying return error dot issues dot map where we get each individual issue take over its message.
3:20:46And once we do, we can join them all together by commas. Hopefully, this makes sense. But if it's not an array of issues, just one validation error, we will simply pass it over.
3:20:57So JSON stringify errors. So no matter how many there are, we're always gonna present it in a single string separated by commas. And now we're ready to write the logic for creating the user once they sign up.
3:21:10We can do that within controllers. See where within routes, you define the actual endpoints. Within controllers, you define what will happen once those endpoints are reached.
3:21:22So create a new controller called auth dot controller dot j s. The whole goal of a controller is to export a single function that'll do the job when a specific endpoint is called.
3:21:35So in this case, I will export a new function called sign up that is gonna be an asynchronous function that accepts a request and a response and the next. I'll talk a bit about what this next means very soon, and then it does something.
3:21:51I'll open up a try and catch block as before. So if something goes wrong, we can properly log it and handle it. First things first, I'll turn the logger on and log an error saying something like sign up error so we know exactly where it happened, and then we can provide additional error messages.
3:22:11And then specifically, if the error dot message is equal to user with this email already exists, in that case, we can return some other status by saying res dot status of four zero nine, which is an exact HTTP status that says basically what that message says, user with this email already exists, and we'll provide more information to that user.
3:22:38But otherwise, we're simply gonna say, hey. Take this error and forward it over by using the next functionality. Next is typically used when something is considered a middleware or when a specific action will be called before or another action so that it passes the torch in a way that it'll execute some logic, and then it'll pass it over to the next function in the chain.
3:23:01It'll make a bit more sense later on once we put the sign up where it needs to be. Okay. So now let's focus on implementing the logic of the sign up.
3:23:07First, we wanna validate the data that is coming into the form or into this endpoint by saying const validation result is equal to sign up schema dot safe parse, and we're gonna pass the reg dot body.
3:23:23This reg body will contain the form data that the user has typed in when trying to make a request. If no validation result dot success, so if something went wrong, we will return a ResNet status of 400 alongside the following JSON payload.
3:23:41It'll have an error of validation failed, and it'll also have details within which we're gonna use our format validation error utility function.
3:23:53Into it, we can say validation result dot error.
3:23:58So the user will know exactly what went wrong and what they have to fix. But if everything went right, we can get access to the name, email, and the role that the user has submitted through the form by destructuring them from the validation result dot data.
3:24:14Then within here, we'll have to call our auth service to actually create an account. And then later on, we can use the logger functionality to simply give us an info message of something like user registered successfully, and we can even render their email.
3:24:30So let's make sure this is a template string. Finally, we'll respond with a status of two zero one, which means user created, and I'll pass an additional JSON payload of message user registered, and we'll pass over the user information.
3:24:47For now, we can pass an ID of one. We're faking it alongside the user's name, email, and the role that got created.
3:24:56Okay. So let's test this out. If I now head back over to my HTTP client and head over to sign up, if I make a request, we'll see just a regular sign up response.
3:25:07That's because we haven't yet hooked up our controller to our route. So to hook it up, it couldn't be any simpler.
3:25:15What you need to do is just remove this callback function because we no longer need it, and what you need to instead do is simply call the sign up controller. So you're basically saying once a user goes to auth API sign up, call this function.
3:25:31So now we will no longer be receiving this. Rather, we'll get a validation error, invalid input, expected object, received undefined, and this is perfectly fine.
3:25:43Imagine that we just submitted a form on the front end, and it was asking us for an email, a name, and a role, and we basically left everything blank.
3:25:54That's not how it goes. We have to get that data from the form and actually pass it through request body. So let's form it.
3:26:02I'll say that this request body, it'll have a name of something like email and a valueofcontact@JSMastery.com.
3:26:13Now if I send it, it'll say invalid input expected string received undefined, invalid input expected string received undefined as well. It is referring to our other two pieces of the form, and that is the name, which I was also missing, and finally, a password.
3:26:31So if we pass a password of something like one two three one two three, you'll see that we have successfully, apparently, right, this is just fake for now, registered a new user.
3:26:43This is looking good, but take a look at our back end. If you take a look at the logs, you can see that the application was listening and that a new post request was made.
3:26:54Now in this case, we were not console logging the password or doing anything with it, but we might as well could have because it got passed in a plain text format, which means that we need to secure it, and we can secure it by encrypting it.
3:27:11For that, there's one best package that most people use, and it is called bcrypt. So simply run n p m install bcrypt, and then we'll head over into another file.
3:27:23This time, we'll actually create our first service. So if you head over into source, services, and create a new file called auth dot service dot j s, here, we will implement the logic of hashing our password.
3:27:40So simply say export const hash password, which is equal to an asynchronous function that accepts the password in plain text.
3:27:50And then it'll open up a try and cache block. In the cache, as usual, it'll try to log an error saying LoggerDot error invalid password.
3:28:03Or, actually, what's happening here is we have an error hashing the password, which shouldn't really happen.
3:28:11And then we can finally throw a new error, error hashing. But if everything goes right, we can try to just return the hashed password by calling the await because hashing it takes some time and is asynchronous and using the b crypt library, so BcryptDotHash.
3:28:33You just have to pass the password and a number called salt rounds. So how many rounds do you want to hash it for? Typically, the default is about 10 to 12.
3:28:43So that's what I'll do. And don't forget to import bcrypt at the top by saying import bcrypt from bcrypt.
3:28:53And this is it. This is a function that will now hash our password. Once we hash the password, we also have to create a new user.
3:29:00So let's create it just below by saying export const create user is equal to an asynchronous function that'll accept an object which we can automatically destructure and get its name, email, password, and the role, which by default will be set to user.
3:29:22Then we can open up a try and catch block. In the catch, we will simply log the error by saying logger dot error, and the error will be all about creating the user.
3:29:35So say error creating the user, and we will throw that error.
3:29:41And in the try, we'll first see whether that user already exists in the database by saying const existing user is equal to d b dot select from the table of users where e q this is coming from Drizzle ORM.
3:30:02Users dot email matches the one of email that we're now trying to create the account for and limited to one user. Make sure to import this DB right at the top coming from config database JS.
3:30:16Also, sure to import the user's model by saying import in curly braces users coming from models user model JS, and I think we're good, then if an existing user exists, so if their length is greater than zero, in that case, we will throw a new error saying user already exists.
3:30:40But if it doesn't, we can start hashing the password and creating a new user by saying const password hash is equal to await hash password.
3:30:53That's the function that we just created above. And then we can get access to this new user by saying const, destructure the array of the response and get this new user out of it, and then call await database dot insert.
3:31:11Insert into the users table the following values. The values of name, email, password, but not the password in the plain text format, but rather a password hash, and then a role.
3:31:26And from this database, return me the following things.
3:31:31So I'll say dot returning ID of users dot ID, name of users dot name, email of users dot email, role of users dot role, and created at of users dot created at.
3:31:51I think you get the idea. Oh, I think I have a typo right here, so let's fix it. Name of users dot name.
3:31:59And I will actually put this into multiple lines so it's much easier to look at. Values in one line and returning in another. Finally, once we create this new user, we can log it out by saying logger.info user with this new email created successfully, and then we wanna return that user from this service.
3:32:20These services are like just additional utility functions that we're gonna call within our controllers. So now let's head over into our auth controller. That's within auth.controller.js.
3:32:35Here, I left some space for this auth service. And first, we wanna call the create user. Const user is equal to await auth service.
3:32:45Oh, it looks like it forgot to export it. So head over to auth service.
3:32:51And as you can see, we're exporting these services one by one. So what you can do instead of calling auth service, you can just simply call create user, and it should auto import it from services auth service.
3:33:05To create user, you can now pass everything it needs such as the name, email, password, which now will automatically get hashed, as well as the role.
3:33:15This password is coming from the validation data, of course. And once we have the user, we can create their JWT. So const token is equal to JWT token.
3:33:28Make sure to also import this. Dot sign, pass the ID of user dot ID, email of user dot email, and role of user dot role.
3:33:42Finally, we can take that token and set it to the cookies by saying cookies, again, imported from our utility cookies, dot set.
3:33:53We created this method on our own, and we wanna set the entire response with the name of token and a value of this token we just crafted by using JWT sign. Once we do that, we're successfully logging this in, and now we no longer have to fake this user ID.
3:34:09We now have a real one, user dot ID. Name is equal to user dot name. Email is equal to user dot email, and role is equal to user dot role.
3:34:22Perfect. And as you know, we're calling this controller right here within routes.
3:34:28So if you head over into routes, you can see we're calling the sign up. And if you make a request to this endpoint but with proper data, In this case, I'll be using just a regular text element so we can pass a regular JSON object that looks something like this.
3:34:46Name of admin, email of admin@JSMastery.pro with a password of 123456 and a role of admin, you can now send that request.
3:34:57And you'll get back a two zero one saying user registered with a role of admin, and you can also create another one, maybe Adrian, Adrian JS Mastery Pro, this time with a role of user, and you'll see that the ID will be incremented.
3:35:13So this time, it'll be saying two. And for each one of these requests, you can also see the response.
3:35:20So if you click right here, you can see all the information about this response and take a look at this field called set cookie. You can see that the cookie actually contains the actual JWT.
3:35:32So within our application, we'll know this user got authenticated. Amazing job. This basically handles the majority of the authentication setup.
3:35:43We have the sign up and user creation. Now we also gotta figure out how to do sign in and sign out, which are, of course, essential parts of every good authentication. So we'll do that soon.
3:35:56Before we dive into implementing REST of authentication features and running DevOps tasks on a real world app, I wanted to stop for a second and show you a tool that I've been using a lot lately. It's WARP, the fastest way to build and ship applications using AI right inside a single environment.
3:36:15It includes a terminal, a code editor, and an agent hub.
3:36:20So instead of memorizing commands or juggling through different docs, you can just type in what you want in plain English, and Warp will figure out the rest. Execute the commands and just get the job done. With Warp, you can chat with any AI, all the top models including Claw, GPT five, and more, and it helps to plan architecture, explain, or review your code base.
3:36:43You can also write and edit code in line with smart suggestions. You can run multiple AI agents at once to speed up the results and automate entire workflows. So typing something like undo last merge actually rolls back the changes in seconds.
3:36:59In short, you describe what you want, and the warp does the heavy lifting. And the best part is the developer experience of having everything together, a terminal, a code editor, and agents all in one place. No need to have three separate browsers, an editor, and 10 tabs open just to get some output from AI agents.
3:37:19And talking about pricing, you can try it all for free. They have a pretty generous free plan. But if you need something bigger, for a limited time, WARP is offering you, yes, you watching this video, the pro plan for only $1, which is normally $18 per month.
3:37:37So click the link down in the description before it's gone, and we'll use it very soon to 10 x our productivity. Let's dive right in. To get started, click the link down in the description and download Warp.
3:37:48Once you download and log in to Warp within your device, you'll see a pretty empty screen that allows you to create a new project, open a repo, or clone it. But the real magic happens right here at the bottom. Here, you have two different modes, a terminal and an agent mode.
3:38:06Then you can select any AI agent you wanna use for this specific task. In this case, I'll go with auto, which is Cloud Force on it since it's currently the best option for coding tasks. Then if you press a forward slash, you'll be able to see some options, such as adding MCP servers, adding prompts, rules, and more.
3:38:25Oh, and there's also a voice input mode. So if you're more of a vibe coder who prefers to code by speaking over typing, well, you can do that too.
3:38:33Here, you also have some directories so you can switch between different repos and attach additional context from different folders. So let's c d into the repo we've been working within. It's acquisitions.
3:38:45And immediately, warp is asking us whether we wanna optimize it for this code base. And for sure, I'll select optimize.
3:38:52It'll do that by indexing this entire code base, and it can also create its own MD file. So I'll say, sure. Go ahead.
3:38:59As soon as we enter this repo, you can see that now it tells us the version of node we are running, where this project is located. We can see more Git metadata, such as the branch and how many changes we made, and we can also attach some additional context.
3:39:13Now while it's doing its thing, I'm gonna open up the warp drive on the left side right here. You can open it up by just pressing this button at the top. Under personal, here you have different things, such as the rules you can add, the MCP servers, and the getting started notebook.
3:39:29And it's not limited just to personal projects. You can also have your entire team join you. And while I was clicking around, you can see how it actually opened different windows all within a single environment.
3:39:40This is the beauty of this developer experience using Warp. No longer do you have to have 10 different apps opened up. It's just one.
3:39:47So let me go ahead and create a new team that I'll be using for this project. I'll call it acquisitions since that is the name of our app, and let's start with JSM acquisitions.
3:39:58Once you create it, you can start adding some workflows or notebooks right here, or you can press this plus icon and then start with a new prompt. Within the video kit down below, I'll give you access to the complete prompt that you can type right here so we can set it up in the same way. Let's start by giving it a title of codebase architect explainer, and then we can give it a description saying that this is an AI prompt that studies any codebase and produces a clear structured explanation of its architecture and how it works.
3:40:28And then you can pass the prompt itself, which you can find within the video kit down below. Now when you create this code base architect explainer prompt, it'll be added to your team or to your personal project.
3:40:41Then when you head back over to your terminal and type forward slash, you'll be able to see the list of your different prompts, and one of those is the code base architect explainer. As soon as you type it, it'll automatically select it, and the only thing you have to do is simply press enter.
3:40:57So no need to type in long prompts manually or scout your past ChatGPT history to find them. Now we can store them all in one place and re execute them whenever you need to.
3:41:08The first thing that it is doing is reading the files that we have, and as you can see, immediately, it started explaining what is currently happening within our code base.
3:41:19Of course, you took the time to build it so you already know what's happening, but, of course, seeing an overview of the project structure, for example, and of all the folders we created is always super useful. We get a complete breakdown of all of the components that form the application together and even the data flow of the application where a client makes a request, then we have the express middleware to the route handler to the controller to XOD to services, and finally, we return the response.
3:41:50So this is perfect in case you wanna study it in more detail or ask it more questions. I'm just amazed. I mean, it even gives us a sample request execution that we can send over to test this API.
3:42:02This is great, and this is just one single prompt that I gave it, and it immediately spat all of this back within this a bit unorthodox terminal like experience.
3:42:14So, like, it's not a typical chat, and it's not a terminal either nor it's an editor, but it's all combined into one.
3:42:25And that feels so familiar to me as developer. So let's see what else warp can do besides just explaining our code base. And I think to truly test warp to increase our speed as developers, I wanna ask it to implement what was gonna be the next step for me to do manually within the application.
3:42:43Remember, we just created a route for the sign up and a sign up controller, but we haven't yet implemented controllers nor services for sign in and sign out routes.
3:42:57So let's ask Warp to do it for us. Now check this out. I will run a clear command to clear what I'm currently seeing within my terminal window, and then I'll switch over to the agent mode.
3:43:09And you can immediately start typing of what you want it to do. I'll give it some background such as you are a back end developer working on an Express JS app with auth features.
3:43:25Your job is to extend the auth service and controller to support user login and logout.
3:43:35I think that this even might be enough, But just to make sure that you can follow along and see the same exact output that I'm having, I took a bit of time to write a bit more detailed prompt. Just so I don't have to type it out manually, I'll give it to you within the video kit down below, so simply copy it and paste it right here.
3:43:52I provided it with a bit more info on what it can do to make this happen and then saying that it needs to implement these two functions. So now simply press enter like you're running a terminal command. In agent mode, warp immediately started warping and told us that it'll help us extend the authentication service controller with these two additional functionalities.
3:44:16It'll do some things on its own, but for some things, it's gonna ask us whether it's exactly what we want. So for example, it created this compare password function, and it put it within services. This is exactly how I want it to look, and this is exactly where I want it to be.
3:44:32So I'll gladly accept the changes by simply pressing enter. And, of course, if you wanna edit something, you can do that by pressing command e or pressing the edit button, and then edit it, and then submit it, and then it'll continue producing the code leading to the output.
3:44:48Same thing right here for adding the authenticate user to the auth service. In this case, it looks like it's actually applying a fix to the code that I wrote that I didn't necessarily anticipate, and that is that I forgot to put the await right here before d b select.
3:45:03So this was a crazy catch by warp. It didn't only do what I asked it to do, which is to add additional features to the auth, but it actually fixed a mistake that I made on my own. So I'll definitely apply those changes as well.
3:45:16And only now it's starting to extend it. I'll press enter a couple more times, and let's see what it comes up with. It's properly implementing the schemas as well as the two controller functions, sign in and sign out.
3:45:30And finally, it'll update the routes to use the new controller functions. That one should really be quick. There we go.
3:45:38It removed all of these lines and simply imported sign up, sign in, sign out, and triggered them when we reach those endpoints. It'll now read through all of those files and check the implementation.
3:45:49And it says, perfect. I've done it. Gives me an overview of exactly what it accomplished, which code it added, and how it updated the routes, and finally, which features were implemented.
3:46:03Finally, it also gives me an example of how I can test it. So let's test out the implementation. I've opened up h t p y, and instead of sign up, I'll head over to sign in to see whether that works.
3:46:16And now I need to sign in with my email and password. And before I send it, let's make sure that our application is running on localhost 3,000. And then we got user signed in successfully, which means that the sign in function has been implemented.
3:46:29And now we can also try the sign out. It actually told us what we have to do. No body required.
3:46:36We just cleared the cookies by making a post request to sign out. So if I do that without passing anything within the body, it says user signed out successfully.
3:46:45To be honest, I'm just blown away by not even the simplicity of using it, but just by developer experience of having it all within a single environment.
3:46:57And, of course, since it's connected to a repo, all the changes that have been made are immediately visible within our other editor as well. That's amazing. So now if I switch over back to the terminal, I can clear it.
3:47:11I can run git add dot git commit dash m and say implement auth, and then push it.
3:47:21All from within a single terminal like experience where five seconds ago, I was speaking with agents. Oh, and wanna see something else? If I press right here, I can open up the project explorer and actually browse my entire application and all of the code.
3:47:35And I can then hover over that code to add it as additional context. And I can have the files on one side and the terminal and the agent on the other. It really feels like a full experience.
3:47:45So with that in mind, authentication is now done. And in the next lesson, let me show you how we can secure it.
3:47:54Now that we've implemented authentication with proper logging and monitoring, it's time to make it more secure. And for that, we'll use ArcGit.
3:48:03See, a back end without security, rate limiting, or bot protection isn't just unfinished. It's also unsafe because these exposed APIs with no safeguards are left to abuse, spam, and even DDoS attacks.
3:48:18All it takes is one bad actor to overwhelm your system and take it down. Thankfully, ArcGit has built in features for Node. Js applications that protect us.
3:48:28ArcGit shield automatically protects your apps against most common attacks, including the top 10 most popular attacks. Then without rate limiting, a single client can flood your servers with requests and starve out real users. But with ArcGit, you can limit the amount of requests that each user can make.
3:48:46There's also bot protection so that you can stop automated scripts that exploit your endpoints or scrape data or brute force credentials. And also, you can protect the sign up form by combining all of these things together. If you think about it, these attacks aren't edge cases.
3:49:02They're everyday realities, and making your app secure is not only smart, but it is necessary. More than that, it wouldn't make sense not to make it secure as it is so simple doing that using Artjet.
3:49:15So click the link down in the description, and let's set it up together. Sign up for free. You can go with GitHub.
3:49:21As you can see, I already have a couple of projects that I'm hosting over on ArcGit, so I'll create a new site right now. You can name it something like j s m underscore, and we're gonna use the acquisitions. Or you can call it DevOps as well and create.
3:49:35Now immediately, you're given a key that you can copy. So let's do that first and add it over to your dot e n v. Right below, you can call it ArcGit and add the ArcGit underscore key equal to this key right here.
3:49:52And then we can fold the setup for Node and Express. So just click right here, and it'll give you instructions on how we can set it up within five minutes. We have already installed Express, so what we have to do is install at ArcGET forward slash node and at ArcGET forward slash inspect.
3:50:09Now it'll be up to you whether you wanna keep using your current IDE or editor or you wanna switch over to WARP and run everything there. Both ways are totally fine. I'll proceed with WARP just to see how it all works.
3:50:22So I'll clear everything and install the necessary packages, ArcGit node and ArcGit inspect. While that is installing, the next step is to, of course, set our key, which we have already done, and then add some configuration rules.
3:50:36So go ahead and copy this file from the documentation, and then open up your project explorer. Within here, you can head over to config and create a new file, call it argjet dot j s.
3:50:49Within this file, you can paste what you just copied, but we don't have to set up a new app right here. We just need to set up an instance of ArcGit with their own rules. So that's gonna be import ArcGit shield detect bot and sliding window.
3:51:03This is one of the rate limiting methods, and then we don't need this spoof bot or express. While setting up our instance, we first need to pass over our key coming from process.env.arcgit key, and then we can start defining the rules.
3:51:17First of all, we have this shield rule which protects your app from the most common attacks, such as SQL injections, which is super useful in this case, and I will leave it live. Then we have the bot detection rule where you can also say live or you can also use dry run to log only, and then we're specifying which bots should we allow because not all bots are necessarily malicious.
3:51:42Maybe you want some bots within your application such as for monitoring and so on. In this case, we are allowing the search engine bots, such as Google or Bing, to crawl our application so we have good SEO. This is incredibly important, and we can also turn in link previews, for example, for Slack or Discord.
3:52:00So I'll say category preview as well. Finally, instead of using a token bucket rate limiting algorithm, we're gonna use a sliding window.
3:52:10The way it works is you say sliding window, and then you can define the mode of that window. I'll set it to live.
3:52:18And then we can set the interval to two seconds, which will refill the sliding window every two seconds, and it'll allow for a max of five requests per interval. For example, you can increase this interval to maybe one minute so you allow five requests per minute.
3:52:36It's totally up to you. Now since this is just the configuration file for ArcGit, in this case, we don't necessarily need to have this request right here.
3:52:45We're gonna do that later on from within our application, or we'll add it as part of the middleware. So I will just delete this part from here.
3:52:53And instead, I will simply export AJ as this new instance of the ARPJET configuration that we've just created. And then we can move over to creating security middleware by heading over to source middleware and creating our first middleware, which will be called security.middleware.js.
3:53:13Within here, we can now import this instance of ArcGit that we created coming from config forward slash arcgit.js, and we can create the security middleware, which is gonna be just an asynchronous function that accepts the request, the response, and then the next.
3:53:30So we can forward it over to the next function in the chain. That's what the middleware functions are for. And then I'll open up a new try and catch block.
3:53:39In the catch, we can just lock the error by saying console dot error arkjet middleware error, and then we can actually log in right here.
3:53:50And then we'll also send the ResNet status of five hundred dot JSON with the error of internal server error if something went wrong with a message saying something went wrong with security middleware.
3:54:07But if everything is going well, here, we can set up all the different limits and all the different security measures that we want ArcGit to implement. So first things first, we gotta figure out which user are we working with. Is that user a guest, an admin, or a regular user?
3:54:26So say const role is equal to rec dot user question mark dot role, and by default, we can set it to guest. Maybe they're unauthorized.
3:54:36Then I'll create two empty variables of limit and the message that we wanna display, and then I'll open up a switch statement that's gonna change the limit and the message depending on the role. So if the role is set to admin, in that case, we're gonna set the limit over to 20 requests.
3:54:58And we'll set the message to be equal to admin request limit exceeded 20 per minute.
3:55:07Slow down. Now we can also add a break statement right here to end this case, and we can duplicate it two more times below. Okay.
3:55:17Just like this. For the second case, we're gonna have the user. We're gonna limit them to 10 requests, and then we'll say user limit exceeded.
3:55:26That is about 10 per minute. Also slow down. And finally, the guest will be allowed five requests per minute.
3:55:34So this automatically tells you how extensible the security measures that you implement within your applications are. You can give specific types of users more requests. If your API is paid, maybe you can have different tiers.
3:55:47And then if somebody's paying more, you can actually charge more money for more requests per a specific window of time. After that, we can define a new Arjeet client by saying a j dot with rule, and we'll provide a rule of sliding window and pass in a mode of live, an interval of one minute, a max, which is the limit defined above, and also a name of that rule, which is gonna be set to a template string of role rate limit.
3:56:21So this is the new rule that we are applying. And we can even take it a step further by adding logging for when ArcGit figures out someone is a bot.
3:56:31So say if decision this is very important.
3:56:34So if ArcGit decides that something is a bot and this decision is coming from client protect, so as soon as we try to protect this request, cause decision is equal to await client dot protect this request that we're trying to make.
3:56:52So if this decision is denied to make that request and if the decision dot reason dot is bought, obviously, then they're a bot, then we can use the logger functionality by importing the logger at the top.
3:57:08So we can say something along the lines of import logger from hash config forward slash logger dot j s.
3:57:17And we also need to import this sliding window algorithm by saying import sliding window coming from at argjet forward slash node.
3:57:28So now if we are denying our request because of a bot, then we can use the logger dot warn feature, and we can say something like bot request blocked and then provide additional information such as the IP address of rec dot IP, user agent of rec dot get user agent, so we know which user agent tried to make that request, and then the path that this request was trying to be made to.
3:57:58So that's gonna be rec dot path. And then, of course, since we have declined them, we can also return a res dot status of 403 with the following JSON message.
3:58:09Error of forbidden. We are blocking you. And we can also add a message saying something like automated requests are not allowed.
3:58:20Perfect. It is that easy to implement bot detection.
3:58:25Now we can duplicate this request down below, including this logger warn and the return statement. And this time, instead of checking for bots, we can check if it's denied, but if the reason is shield.
3:58:39So this has to do with the 10 most common attacks. In this case, I'll say shield blocked request, and we'll say the same thing.
3:58:48Give me the IP address. Give me the user agent. Give me the path.
3:58:53And in this case, we can also try to log the method that the user is trying to make. So that can either be post, put, update, get, and so on.
3:59:03And finally, I will do it one more time by once again copying this bot detection. And instead of checking whether it's a bot, I'll check whether it's a rate limit. Remember the limits above?
3:59:16So in this case, we'll say rate limit exceeded, and we will log the same things again. And instead of saying automated requests are not allowed for the shield blocks, we can say something like request blocked by security policy.
3:59:31And then for the third one, we can say something like too many requests. Finally, if we pass all of these if statements, that means that we are allowed to make a request, so we can finally just say next.
3:59:42This is good. This middleware did what it's supposed to. It tried to secure the application.
3:59:47There's nothing to secure it from, so go ahead and make the request. So now back within our app dot js, we can add another middleware. Right here above all of our requests, I'll add the app dot use security middleware, and make sure to import it right at the top by saying import security middleware coming from hash middleware /security.middleware.js.
4:00:16And I think now we get a better idea of what these middlewares are. So these are all the different types of middlewares that we're injecting in between our requests that extend our app with some additional functionality, such as this one that is applying security to our application.
4:00:30Now to test it out, head back over to h t p y, and let's try to make a request to forward slash health, and we can make it a get request. So if you send it out, we get a connection refused. That's fine.
4:00:42Let's not forget that we can immediately open up a new tab, make sure that we are in the right repo, and then just run NPM run dev to spin up our application. Now it looks like I have a typo right here that is in security middleware.
4:00:56So if I quickly head over to security middleware, we can fix it in one go. It is on line 27.
4:01:04It looks like I had one extra curly brace, and then we also have to close this function right here after we close the catch. Now if we head back over to the config file of the ArcGit, same thing here, we have one extra curly brace. And back within our app, we have to properly import security middleware.
4:01:23And we are properly importing it, but we forgot to export it from here. So just do export default security middleware.
4:01:32And now we are running and listening on localhost 3,000. And as you can see, ArcGit is listening. So back within our application, if you try to make a request to forward slash health, you'll see that everything is good.
4:01:44And now if you rapidly make requests to health, you'll see forbidden too many requests. This means that you've just successfully added rate limiting to your application. And if you head back over within your terminal, you'll be able to see something like this.
4:02:00All the infos are here, but we also have different warnings from our logging system that say rate limiting exceeded. We can see the IP, the path, the service, and the user agent, in this case, h t t p y.
4:02:13Oh, and if you head over within your ArcGit dashboard, you'll see that at first, it allowed a couple of requests over to forward slash health, but then as soon as we hit the rate limit, it starts denying them, saying that the limit is a max of five. At the same time, it also checked out all the different rules that we set for the rate limiting and bot protection with specific allowances.
4:02:34But in this case, we did not break any bot protections, but just so know that you are protected. So if somebody tries to hack your application, make some malicious requests, or just tries to use bots to scrape your API, ArcGit will protect you.
4:02:49That's it. It was super seamless to add security to our application. So let's go ahead and commit it by saying git add dot git commit dash m secure our API with ArcGit and push it.
4:03:03Perfect. Now you know how you can secure any API that you create in the future, and you can do the same thing for different frameworks such as NextJS. In this lesson, let's dockerize our application.
4:03:17You've learned a lot about how Docker works within the crash course part of this course, but now we'll actually dockerize our application for local and production environments so that you can run it within any environment and works for everybody who's running it. We've already talked about what dockerization is. So now let's leverage AI to help us implement it within this project.
4:03:38In the video kit down below, I'll provide you with a special dockerization prompt. Simply copy it and paste it within WARP, and then let's go through it together.
4:03:49It says, you're a senior DevOps engineer. Your task is to dockerize my application that uses a Neon database. The setup must work differently for development and production.
4:04:00Development environment is local, and we wanna use Neon local via Docker. Configure Docker Compose to run Neon local proxy alongside my application, And, again, you can learn more about Neon local right here.
4:04:14That's the beauty of these agents. If something is not working properly, you can just provide it access to a link so that it knows how to work with it. Again, your prompts typically are gonna be much shorter and much less precise than this, but just to ensure that we have more or less the same output that we're getting, I decided to take a bit more time writing this prompt.
4:04:34So let's go ahead and run it. It says, I'll help you dockerize your acquisitions application with Neon database support. Let me first analyze your current project structure and then create the necessary docking configuration.
4:04:47Then it walks us through the tasks that it created for itself. The first step will be to create a Dockerfile for Node. Js application.
4:04:53Immediately, we get back a Dockerfile for Node.
4:04:57Js acquisitions application with a multistage build that works for both production and development. What it's doing here is creating a new non root user for security, then we're changing its ownership and exposing the port.
4:05:11There's even a health check right here, and then we repeat the same thing for development stage. Now you can see for development right here, the command is NPM run dev, but for production, it is manual. It's just pointing to a source index JS.
4:05:26Instead of that, we can head back over to our code, head over to package dot JSON, and like we have added a dev script right here for development, we can also add a start script for production that'll simply be node forward slash source forward slash index dot j s, or I think it's just source forward slash index j s.
4:05:47This way, we're watching for the changes in development, but when you run it on a server, you simply wanna run it once and let it run. Now we can edit this file provided by warp and simply change this over to from node source index j s to simply NPM, comma, start, and apply changes.
4:06:07And now it'll continue doing its thing. Now we've gotten the Docker Compose file, and it looks like it has the configuration for Neon local, the environment, volumes, health check network, and then the Node.
4:06:19Js application right here. It's using all of the best Docker practices to create the files that'll dockerize the application for us. So for the time being, let's go ahead and accept it.
4:06:29We might need to make some small changes or adjustments to it later on, but for now, it's perfect. It says that it is right now on a step three out of eight, and now it created another Docker Compose for production. Note that in here, it is using the Neon cloud database, whereas for development, it used Neon local.
4:06:48Neon local is a feature by NeonDB, which allows you to use Docker environments to connect to Neon and manage branches automatically. In simple words, it's a proxy service that creates a local interface to your Neon cloud database.
4:07:03So let's go ahead and accept this production database too or at least the configuration for it, and now it'll create the environment configuration files. Here we got just some more ENVs. It created them under .env.development, so let's go ahead and accept them.
4:07:17For sure, we'll have to add our own ENVs here later on anyway. And it did the same thing for ENV production. Now it'll automatically update the package JSON with Docker scripts.
4:07:27Now this is a lot of Docker commands that we have to run manually. So in this case, I won't go ahead and accept these changes. Since this is a DevOps course, what we'll do instead is together, we'll develop a bash script that will run all of these different Docker commands in sequence so that we can automate them, which is the whole goal of DevOps.
4:07:51So for the time being, just go ahead and cancel this file right here. And now since we canceled it, we can just ask it to continue. So I'll just type continue, and we can go ahead.
4:08:02Since I canceled the step of updating package JSON, it's trying to do it again. So this time, instead of canceling, I'll go ahead and manually remove these additional commands that it added and just click done. Oh, and make sure to remove the comma after the last command that you have right here.
4:08:17Oh, and the extra curly brace as well. Perfect. So now the only thing that we changed is adding the start script, and now we can apply the changes.
4:08:25As I said, we'll be implementing all of these different Docker scripts within our custom bash script. So more on that soon.
4:08:33Now it'll create a simple Docker ignore file, which we can accept. And like any good engineer, it'll give you a comprehensive documentation for the entire Docker setup that it just implemented.
4:08:45And there we go. Here is a complete Docker setup dot m d so we can see all the changes in markdown. It talks a bit about development and production environment, more about the prerequisites to set this all up.
4:08:57It told us what it did, what we still have to do by adding our ENVs to make it work, how to start up our development environment, and exactly what it will do. Same thing for production and more on using Neon local to run it all together.
4:09:13At the end, it even provided us with a quick start checklist so we know exactly what we have to do to make it work. Let's go ahead and apply those changes and see what Warp has to say next. Oh, it looks like it even provided a quick setup script that we can run to make our process easier.
4:09:29I'll definitely go ahead and accept that. It's also asking us to run it, but for now, I will just go ahead and cancel it so we can go through the changes that it implemented on our own. The next step is to get all the necessary ENVs.
4:09:41The first environment variable we can get is gonna come from Neon, and you can get it by heading over to the top right, pressing your profile photo, heading over to account settings, and then switching over to the API key section where you can create your own personal API key. You can give it a name. In this case, I'll call it j s m acquisitions, and click create.
4:10:05Then copy it, head back over to your application, and go to .env.development. You should be able to see two different dot ENV files, one for production, one for development.
4:10:16Thankfully, Warp left some nice comments right here saying that this is the development environment configuration used when running application with Docker Compose in development mode with Neon local proxy. Okay.
4:10:28So we have some things that we have specified before. We also have our database configuration right here, and what you'll have to do here is put the Neon API key right here that you just copied as well as a Neon project ID and the branch.
4:10:42So we just got the API key. Now to get the additional things, you can head back over to your project, then head over to the settings, and right here, you'll see the project ID. Simply copy it and paste it over here under Neon project ID.
4:10:57And the last thing is the branch name or the branch ID. This one, you can get if you head over to branch overview, and you can copy this branch ID right here and simply override the main branch.
4:11:07Oh, and let's also not forget the ArcGET key. I believe that one was previously in just the dot e n v. So you can just copy the ArcGET key from here and paste it over at the bottom.
4:11:19I'll call it ARGJET and just specify the ARGJET key right here. Now a cool trick or a concept in Docker is that instead of specifying whole ENVs line by line in a Docker Compose file, you can just point it out to the respective file. So head over to Docker Compose dev dot YAML and modify it to point to dot ENV dot development.
4:11:42So here where we have our environment variables, instead of that, you can simply say e n v underscore file and point it to dot e n v dot development.
4:11:54And now it'll get access to all the environment variables. This was done for Neon local, which I will collapse right now.
4:12:00And then below, you have the Node JS application, and you can repeat the same thing. So here we also have the environment. And what you can do is say ENV underscore file, then you can specify the path of the file by saying dot e n v dot development.
4:12:17So now we have updated our environment variables for both services, neo local as well as the app, and we can repeat the same thing for production. So if you head over to docker compose dot prod, you can also remove the environment variables and just say dot ENV dot production.
4:12:36And in this case, you don't have to do it for Neon local. Oh, but make sure that this says ENV file instead of environment. Perfect.
4:12:45Now do you remember how warp generated a set of Docker bash script for us not that long ago? We have it here, but it's pretty detailed and maybe a bit too long, and maybe this one differs from the one that it generated for you. For that reason, I wanna make sure that we have the same bash script that we can run, so head over to the video kit down below, copy the bash script there, and paste it right here.
4:13:08You'll notice that this one is much shorter, and now we can go through it together, and I can explain how it works. This is a development startup script for the acquisitions app with Neon local. This script will start the application in development mode with Neon local.
4:13:23We already covered most of these commands during the crash course part, such as echo, which is basically you can think of it like an alert or a console log in a bash language. It just says what is happening. Then we have a check for the development environment variables and whether they exist.
4:13:39If they don't, we can just say, hey. This is not found. Then please copy them and then proceed.
4:13:44Once again, we're checking if Docker is running. If everything is good, we're creating a new Neon local directory if it doesn't already exist, and then we're adding it to git ignored if it's not already present there.
4:13:56Finally, we're building and starting development containers so that the Neon local proxy will create an ephemeral database branch and application will run with hot reload enabled. Ephemeral means that it is a temporary data store that exists only for a short period of time, and then the data will be lost, shut down, or terminated.
4:14:14Keep in mind, the data here is not persistent. The goal here is only to hold it for some time, and then application will run with hot reload enabled. We then run migrations with Drizzle to make sure to apply the latest schemas.
4:14:27Then we wait for the database to be ready. We use a docker compose command, and then we start a development environment. Finally, development environment, if everything went well, should have started on local host five one seven three.
4:14:39So let's head over to the package JSON. Here, let's add a command to execute our new batch script. Remember when we had all of the other commands right here?
4:14:50Now it's gonna be just one. I will call it dev docker, and it'll simply run s h setup docker dot s h.
4:14:58Make sure to have s h at the start or whatever the name of your s h script is. Maybe it's gonna be something different. As a matter of fact, just so we all have it the same, let's actually create a new folder in the root of our application, and let's call it scripts.
4:15:15Then move this script over to the scripts and simply rename it to dev dot s h.
4:15:25This will be our Docker dev command. So change it here as well. Dot slash scripts forward slash dev dot s h.
4:15:32And now before we run this command, make sure that Docker desktop is running, and then run NPM run dev docker. You can see that it'll start executing the commands from the bash script, such as pulling the changes from Neon local and then building out our entire application. And there we go.
4:15:50Just like that, it successfully built the acquisitions app and then created the network and two different containers for our development environment. No issues, and immediately, the app is running within a Docker containerized environment.
4:16:04Typically, this would take hours to set up, but with proper understanding from the Crash Course part and a bit of help from AI, we were able to get it up and running in a couple of minutes. And immediately, we got a message that our application is listening on local host 3,000. So let's just open it up, and you can indeed see that it is running.
4:16:26You can visit the root route or head over to API endpoint where you get the acquisitions APIs running. Even the health check should work.
4:16:37Perfect. Of course, testing it with h t p y as well will do the trick. So if you try to hit a 3,000, you'll get a successful response.
4:16:46This is not running from our terminal or from warp terminal. This is running directly from a Docker container. But now let me show you something else.
4:16:54Instead of just making a GET request to hello acquisitions, if we head over to 3,000 API sign up and try to create a new account.
4:17:07So I'll head over to a text, and I'll pass over all the important information such as the name. I'll go with something like Adrian.
4:17:16And we are in JSON, so double quote a string, so all the way. I'll enter my email. I'll make it a bit different this time.
4:17:23Contact at JS Mastery dot com. And let's not forget about the password as well. I'll do 123123.
4:17:31Oh, and I forgot to add auth before the sign up. Now the reason we got this error right here, it's mentioning a failed query, but, basically, what it's saying is, hey.
4:17:41These users don't exist. I cannot find this table or the schema for the users. And the reason for that is we have to reconfigure our database connection so that it actually points to Neon local for our local development purposes.
4:17:56You can do that by heading over to source, config, database JS, and then here, we'll have to do setup for Neon local by checking if process dot e n v dot node e n v is set to development.
4:18:14In that case, we'll do some further Neon config, which you can import from Neon database serverless by fetching the endpoint that we're trying to reach.
4:18:24And in our case, we're setting it to well, you can see what Docker config is saying. If you head over to Docker Compose dev under Neon, you can see that its port will be five four three two.
4:18:37So we can set the endpoint to h t t p colon forward slash forward slash neon dash local colon five four three two forward slash SQL. We can also set the Neon config dot use secure web socket and set it to false, and we can also set the Neon config dot pool query via fetch, and I'll set it to true.
4:19:05I found these values in the Neon documentation for setting up the Neon local environment. And that's it.
4:19:11So now if you head back and retry the request, there we go. The user got registered, which is amazing.
4:19:19ID number 3, Adriancontact@JSMastery.com. This is great because it seems like we just created our third user, and everything is connected to our original DB. But if you head back over to Neon and reload your tables, you'll see there are only two users that we created before.
4:19:36So it seems like our database is not connected, or is it? Actually, we're connected to the local instance using Neon local Docker connection so that we don't mess with production databases. This means that all the changes that you make right here for our local environment are gonna stay local, and it's impossible to break anything in production exactly how it should be in real applications.
4:20:00Now how would you go about actually dockerizing this for production? Well, there's another script that we can add. So if you head over to root, scripts and create a new script, which you can call prod dot s h, you can find this script within the video kit down below.
4:20:17It's very similar to the dev script, almost exactly the same, but instead of using Neon local, it's using a regular database connection. We won't go ahead and run this right now because it's gonna work in the same way that the dev one did. But before you're running this later on, just make sure that your environment production variables are also properly set.
4:20:38Here, you'll put your real database URL that before we kept within our original dot e n v. And you can also add the ArcGeck keys, JWTs, and so on.
4:20:48And then you're almost ready to run it. You just need to add it to your package JSON by saying prod docker, and then you're gonna run this different command, s h dot slash scripts forward slash prod dot s h, and you're ready for production.
4:21:05I mean, the fact that we implemented dockerization to our application so quickly and in such a simple way is just crazy. We just needed a good prompt, and we let Warp handle the rest. So I'll definitely go ahead and commit this over to GitHub right now by running git add dot git commit dash m implement dockerization and git push.
4:21:29Once you understand theoretical concepts and the reasons why we do things we do, then it's very simple to turn it into a clear prompt with detailed steps, and WARP and its army of agents will do the rest. It's that simple. You focus on architecting, and AI will take care of the implementation.
4:21:50That we've dockerized our application, let's implement all the user routes and all of the CRUD functionalities regarding users. We can start by creating a new file right within source routes.
4:22:04And then within routes, we can create a new file called users dot routes dot j s. And then as with the auth routes, we can create a new router by saying const router is equal to express dot router.
4:22:20And then we can define a router dot get to get all the users. So once somebody points to this route, we'll get a request and a response, and we will just run the res dot send get forward slash users.
4:22:34I will duplicate it three times, and let's not forget to export this router. Now for the second one, I'll do a get request, but not to forward slash users, rather the forward slash users forward slash colon ID, which means that this will give us the details of a specific user.
4:22:53Then we can do a put request so that we can modify a user profile, and we, of course, have to know which user we're modifying. So that, again, is gonna be the ID property, but this time a put request.
4:23:06And finally, a delete request to a specific user ID. So we wanna delete a specific user.
4:23:15Perfect. So now that we have those routes, let's head over into our app.js. And alongside getting the auth routes, we can also get the forward slash API forward slash users routes, and that's gonna refer to the user routes coming from the file we just created.
4:23:34Perfect. Now we'll also need to create some additional services to fetch and create all of those users. So head over into source services and create a new service file called users dot services dot j s.
4:23:53And we'll need to define the function that'll give us back all the users. That'll look something like this. Export const get all users is equal to an asynchronous function that will have a try and catch block.
4:24:07In the catch, we will, of course, just use the logger functionality to log the error, something like error fetching all users or error getting users, and then we can also throw this error. And then in the try, we'll actually fetch all users by saying all users is equal to await d b coming from database JS file dot select.
4:24:32So we wanna select specific fields from the database. Specifically, we wanna get it dot from the users table or the users model.
4:24:41So simply import that users model. And then within the curly braces, you can pass which field you wanna get back from the users, such as ID.
4:24:49It's gonna be users dot ID. Then we can do the name and the email. We can also get the role of users dot role created underscore at is users created at, and updated at.
4:25:03These are all the fields that we need. And once we get those users, we can simply return them. So I'll say return all users.
4:25:12Perfect. In this case, it's saying that this local variable is redundant, so you don't even have to put it in a variable. What you can just do is add a return statement right here because it's the same thing.
4:25:25We're just returning the output of this d b select, which are the users. Perfect. And now we need to create a new controller for the users.
4:25:34So head over to controller and create a new file called users dot controller dot j s.
4:25:42And let's create a controller that will fetch all the users using the service we just created. Once again, I'll create a new function. Export const get all users is equal to an asynchronous function that has access to a request, a response, and the next function, I'll open up a try and catch block.
4:26:02In the catch, we will use the logger to just log the error as before. And then if there's an error, we can just pass it over to the next function. But in the try, we will use the logger to console log the info that we're just trying to fetch all users.
4:26:18So getting users..dot, and then we can get all users by calling and using await of this user's service that we created, or you can just say get all users and make sure to import it at the top by importing get all users from services users, and maybe we can rename this controller to fetch all users, not to confuse with this service that we have right here.
4:26:45Once we fetch them, you can just return a JSON object with a message of successfully retrieved users, and you can pass the users object equal to all users.
4:26:57And you can also pass the user count, which is gonna be all users dot length. Now it might seem like a bit of an overkill to create a service and then to use that service within a controller, whereas the controller itself could just use this logic that we had here.
4:27:14Like, we could have just done this, and that will be a bit easier to get the users.
4:27:20But keep in mind that as our app scales, so will the controllers and the services and the routes and everything else. So allowing all of these to be separate parts and to breathe independently is very important for scalability.
4:27:33Controllers will handle logging, validation, and more, and the service part will only handle the database parts. That's a clear separation of concerns, which is a must for clean code.
4:27:44So finally, let's head over into the routes, which once again are serving their own purpose. And the only thing you have to do here now is just fetch all users because we've already created a service and the controller for fetching them.
4:27:58So you're now just saying whenever a user goes to, that's gonna be the API users, then simply give me all the users.
4:28:07Now there's two ways of running this app. You can either run NPM run dev to spin it up on localhost 3,000, or you can use the other command we pointed out right here, and that is dev docker.
4:28:21So let's go ahead and run n p m run dev docker, and make sure that you have your Docker app actually running on your device. Once you have it, you can run the command, and it'll start the acquisitions app in a development mode.
4:28:35It'll build it out and do all the Docker stuff that it does. There we go. Neon local is running.
4:28:41Acquisitions is up, and it says that it's listening on localhost 3,000. So if you head over there, you should be able to see hello from acquisitions. But if you head to localhost 3,000 forward slash API forward slash users, you'll get back a JSON output with a message of successfully retrieved users, and you can see a full users array getting returned to us right here.
4:29:05Perfect. Now for all of the upcoming services and controllers, you'll have to do almost the same thing that we did with this one endpoint right now. And to be more productive, we'll use AI to speed up our process and get the job done.
4:29:19So let's head back over to warp. Warp has a special feature called project scoped rules that allow you to create some specific rules just for this project. Think of some custom guidelines or context different for each project.
4:29:33If you open up the sidebar, you'll see rules right here, and you can add global rules or you can add project based rules. So let's initialize a new project.
4:29:43Then in your finder or file explorer, you can find the repo of your application and click open. Now it's asking us, would you like the agent to index this code base, which will lead to more efficient and tailored help? I'll say, yeah.
4:29:56Definitely go ahead and index it. So if you head over to code base indexing, you'll be able to see that it has been successfully synced. And if you open up this file, this is the warp m d file, which is guidance for this specific project.
4:30:09Now if you open up this file, you'll see that warp automatically generated some custom rules for this project specifically. It gave it all the project information and the key technologies that it needs to know when running some additional code. So if there's some additional info that you'd want warp to know when going over your project and when developing additional code, you can just add it right here.
4:30:30For now, we're good. And now back within warp environment, we can pass this new prompt that'll generate all of the additional user CRUD services for us.
4:30:39You can find it in the video kit down below and just copy and paste it here. In simple terms, we ask it to implement all of the other CRUD services. You don't necessarily have to be this descriptive, but in this case, just so we get the same output, I specifically pointed out that we need a get user by ID, update user, and delete user functions.
4:31:00Then we are also implementing some validations and some additional controllers. So let's press enter, and let's let Warp do its thing.
4:31:09It nicely examined the task and split it into multiple smaller tasks, and now it'll ask us for input as it is proceeding. Now there's this little thing right here, the two arrows that say auto improve all agent actions for this task, and I'll turn it on because I believe that it should be able to nicely generate all the user validations, controllers, and services.
4:31:32So let's let it work, and I'll be back in a minute. And there we go. In about a minute, Warp has successfully implemented the complete user crowd functionality for our express application.
4:31:43And like a good guy, he even provided a comprehensive summary of what has been implemented. The user service is right here. These are three separate functions that deal directly with speaking with the database to retrieve the user, update them, and delete them.
4:31:59We also have the validation to make sure what we pass into the API is correct. It fixed the authentication middleware, and finally, it created the user controllers, which actually use the services created above and then pass over to data as API responses.
4:32:16Perfect. We can even see the API endpoints right here so that we can test them out. Back within our code, it created a new users dot services dot JS.
4:32:26If we wanna be very specific, we can rename this file to user dot service dot j s. I think I misspelled it when I was giving instructions. And make sure to also fix the imports for this service within the user controller.
4:32:39It was supposed to be users dot service dot j s. So these services alongside get all users, which we implemented, also include get user by ID, which gets a specific user, update user, and then delete user.
4:32:56And finally, in the controller, we have all the functions that actually use those services and return the data. And most importantly, within the routes, we're now using those three new controllers and each corresponds to its own endpoint.
4:33:12Oh, and look at that. It even left some comments so we know what's happening. For the time being, I'll just test a simple get request to see whether we can get the information for a single user.
4:33:23That's the simplest way to do it right now. We can just copy the ID, head over to local host 3,000 forward slash API forward slash users forward slash and then pass a number of your user like two.
4:33:37And in this case, we get back the authentication required, no access token provided message. Now this response is exactly what we wanted.
4:33:45It means that our verification is working. You cannot get user details of another user if you're not logged in. So let's head over into our HTTP client, and let's log in.
4:33:56So I'll just head over to sign in, and I'll sign in with my details. I think I just need my email and my password.
4:34:05If I do that, you'll see that I get signed in successfully. And then if you expand these headers, under the headers, you will see a token right here.
4:34:17What you need to do here is copy this entire token and then pass it as a cookie header in your upcoming responses. So cookie is equal to this token you just copied.
4:34:30If you do it that way and then head over to API users and you try to retrieve a specific user, like the one with an ID of two, and click send oh, let's make sure that it's a get request.
4:34:43Now the user got successfully retrieved and the data is back, which means that this is working perfectly. So now if I wanted to delete this account, I would just have to make a delete request to it. And if I click send, this is perfect.
4:34:59Check this out. We're getting a four zero three forbidden because we're trying to delete somebody else's account, and we don't have permissions to do that. This works thanks to warp adding middleware to every single one of our routes.
4:35:13Check this out. Now middleware will make more sense.
4:35:18We always have this controller function that we're calling once we hit this endpoint, like the delete right here. Right? But before this function is called, it's calling two pieces of middleware.
4:35:29One is called authenticate token, which needs to make sure that we are authenticated. And only if we are, it pushes it to the next one in the line, which is require role.
4:35:40So in this case, we're saying that we need to be an admin to delete something. If we are, then we can proceed. And finally, then we delete it.
4:35:49Now I'm not sure whether I'm currently logged in with an admin account or not, but let me try to delete a different account, like maybe a user number one. Nope.
4:35:58For all three of these, I don't have permissions to do that. If I try to call all the users so I can see which one is the admin, but even for getting all the users, we don't have the permissions because to be able to read the details of all the users, you have to be an admin.
4:36:14That makes sense. So for a second, I will just remove this function from here and remake the request just so I can see which user is the admin. Okay.
4:36:23So we have these three users right here. But instead of logging into one of these previous accounts, let's go ahead and create a new account that also has admin privileges. So make a new post request to API auth sign up.
4:36:39We don't have to pass any of the headers, but we do have to pass a name, which I will call something along the lines of I am the admin.
4:36:50And then the email will be admin@admin.pro, and we can pass some kind of a password. And don't forget to add a role of admin.
4:37:01If you do this and send over this request, you'll be able to see that this new admin user had been registered. And in the request, we automatically got this user's token.
4:37:12This one is more powerful. So let's replace the old one as we're now logged in as the admin. And now if you try to delete a user, you can head over to API users and then maybe, like, user number one.
4:37:26We wanna delete it. You would make a delete request to it with the proper headers so that the user know who is authenticated, and we don't have to pass anything within the body.
4:37:38So now if you make a request, you'll see user deleted successfully. Wonderful.
4:37:44This means that WARP successfully implemented authentication and role based access middleware, which is working perfectly.
4:37:53Once you understand how things work and once you know what you wanna implement, using AI to do it for you just feels like a superpower because it gets done right and it gets done fast.
4:38:04You might need to tweak it here and there, but more or less, if you start with a proper, scalable, nice code base like the one that we have right here with controllers, routes, services, utils, and more, AI agents will also be able to make a better job of implementing additional features.
4:38:23So with that in mind, let's go ahead and run git add dot git commit dash m, implement users crud, and git push.
4:38:36Perfect. This wasn't necessarily related with DevOps, but I still wanted to include a bit of functionalities within our application so that in the next lesson, we can get back to DevOps, this time in form of testing.
4:38:52And finally, we are at a part where we're gonna dive into testing. Testing is one of the crucial parts of development in general, but specifically DevOps, because you wanna ensure that the entire development process is well tested and predictable.
4:39:08So I'll show you how to use one of the most popular testing libraries out there called Jest. Believe it or not, it has almost 30,000,000 weekly downloads, and installing it couldn't be any simpler.
4:39:22You just run n p m install dash dash save dash dev jest.
4:39:28So we're adding it as a dev dependency. Oh, and we'll also add super test, which provides high level abstractions for testing HTTP while still allowing you to drop down to the lower level API provided by the super agent, 7,000,000 weekly downloads.
4:39:43So let's install that as a dev dependency as well by running n p m install super test dash dash save dash dev. Once that is done, you could head over to jestjs.io and head over to getting started.
4:39:57I'll turn on the dark mode and follow the first steps. After installing Jest, you can head down to additional configuration. And the first step right here is to generate a basic configuration file, and we can do that by running n p m in it Jest at latest.
4:40:13So just run this command, and we'll have to answer a couple of questions. First, do you wanna install the create jest CLI?
4:40:21To which I'll say, yes. Please go ahead. And then it'll ask you, would you like to use jest when running test script in package json?
4:40:28I'll say yes to that. Would you like to use TypeScript for the configuration file? That'll be a no.
4:40:34In this case, we are running a JavaScript application. Then you can choose between Node and JSDOM. In this case, we'll be testing a Node application.
4:40:42Do you want just to add coverage reports? I'll say yes to that. Which provider should we using?
4:40:48In this case, we'll be using v eight. And do we want to automatically clear mock calls, instances, and context and results before every test? I'll say yes to that.
4:40:58And that will have generated a Jest config dot m j s. So you can just open it up by finding it right here within your project explorer and head over into Jest config dot m j s.
4:41:10There's a lot of comments right here for all the different things that you might wanna turn on, and there's some things that are not commented out, such as clear mocks, collect coverage, coverage directory, and so on. It's a very long file, but as you can see, most of it is just commented out. One thing I wanna point your attention to is this test environment variable where it says the test environment that will be used for testing.
4:41:32In this case, we wanna switch it over from just environment node to just node. We'll be running our tests there.
4:41:39Then you wanna head over into package dot JSON. And under imports, you can add an import alias for the source folder as we'll need that when writing tests. So you can say hash s r c forward slash everything will point it to dot slash source forward slash everything.
4:41:57And another thing we'll have to do is add the test script. Right now, it just says test Jest, but instead, we'll say test and provide some additional options, such as node underscore options is equal to dash dash experimental dash v m dash modules.
4:42:17And then we'll actually run the Jest command. The reason why we're doing that is so that it works with the type of node applications using the module.
4:42:25So new e s six import statements. That's why we have to provide this experimental VM modules. Great.
4:42:32With that said, let's navigate over to sourceapp.js. And then within here, we'll need to add some default middleware logic that will catch all endpoints that don't exist or are not defined. We'll use this later on in the tests to check if a request has been made.
4:42:49It should not throw an error but show a meaningful message. So right below these two routers, I'll say app. Use and immediately render a reccan res because we don't need the path as it'll act as a catch all route.
4:43:03So immediately, we can just return one thing from it. That'll be a res dot status of four zero four, which means it doesn't exist or could not be found, and then we'll return an error saying route not found.
4:43:18Perfect. We'll use this later on within our tests. So let's go ahead and create a new tests folder.
4:43:25By heading over into our acquisitions app, we can create it in the root of our application. And let's do it via terminal. Make sure that you are currently in the acquisitions folder, and then run m k dir, which is to make a directory, and call it tests.
4:43:40Within tests, you can then create a new file, which you can call app dot test dot j s.
4:43:48And within it, we can write our first sample test. So let me teach you how we do tests in Jest. The way you approach creating tests is always describing what you're trying to do.
4:44:00So you can say describe and then define what you're describing. So in this case, API endpoints. And then you can create a callback function within it.
4:44:09Then you can describe again what should happen. In this case, we wanna get a forward slash health route. And then as the output of that, you say what should happen.
4:44:21It should return health status.
4:44:25And then you define a new asynchronous callback function after that and make that actual request by saying const response is equal to await request to which you pass the app, and that app can be coming right at the top by importing app from hashsrcapp.js.
4:44:46And then once you make that request, you can make a get request to forward slash health, and then you can say expect a response of 200. Then we know that our app is working.
4:44:57Another thing we can do is expect the response dot body to have property of status, which is set to okay.
4:45:07So I think you can already get how intuitive writing gist tests is. You're basically saying, describe this.
4:45:14It should do this, and we are expecting that it will do this. And I will repeat this, expect two more times. So let me just paste it below and indent it properly.
4:45:27For the second time, we're expecting the response body to have a time stamp because that's also one of the things that we're returning. Oh, in this case, we're missing the okay at the end, so let's end it properly here.
4:45:39And, finally, for the third time, we're expecting it to have the uptime because that's also another properties that we have there. And now we can repeat this inner describe in case you wanna add another test. This time, we can say that you wanna make a get request to forward slash API, and it should return an API message.
4:46:01So we're making a request to forward slash API expecting a 200 response, and then we're expecting a response body to have property message, which is equal to a string of acquisitions.
4:46:16And instead of just typing it out, we can head over to the AppJS to see what we're actually responding. We're responding acquisitions API is running.
4:46:25So this is the exact thing we wanna see. Perfect. And we can do it one more time by duplicating it and saying that we are trying to get a forward slash nonexistent route, and it should return four zero four for nonexistent routes.
4:46:43So if we make a request to forward slash nonexistent, it should return a four zero four. And we're expecting the response body to have a property of error, say something like route not found without an exclamation mark.
4:47:00Perfect. And this request right here has to be imported because that's part of supertest. So import request coming from supertest.
4:47:10Save it and open up your terminal. Or conveniently enough, we're immediately within our terminal right here since we're within a warp environment. So just run NPM run test, which will run our suite of tests.
4:47:24There we go. There's gonna be a lot of stuff right here, but the most important thing is that our tests within the app dot test dot j s file all pass. We get health, API, and nonexistent all return exactly what we expected that they will return, so three out of three passed.
4:47:42This will also generate the entire coverage of the whole testing. And to see it in action, you can head over to coverage, LCOV report, and then open up its index HTML.
4:47:52In the browser, of course, not here. Once you do that, you'll be able to see something that looks like this, which tells you exactly how well tested your code is. The way in which we wrote tests today, even though they're minimal, is still the way it works in a real production environment.
4:48:06Of course, this is just the beginning. As you continue building your application, you'll be writing more advanced tests and making sure that it's resilient against all the errors.
4:48:15So if you'd like me to create a more detailed course on testing, let me know in the comments down below. Very soon, we'll implement this test as part of a CICD pipeline so I can show you how we can run it automatically as soon as you push to your application.
4:48:30That's the beauty of DevOps. So let's do that next. Before we jump into building the remaining features of the application, let's take a step back and set up CICD pipelines for linting, testing, and building our Docker image.
4:48:46We're not doing this just because it's a DevOps focused video. It's because in a real world workflow, you don't leave CICD for the very end.
4:48:55The whole point of the pipelines is to catch issues early and ensure your code is reliable as you go. I've been scrolling through the CICD actions of JS Mastery Pro. That's the repo behind the jsmastery.com platform.
4:49:08But it's not just us who are doing these actions. If you take a look at Next. Js's official repo, you'll see that they have ran almost 300,000 workflows, and some are running right now like a minute ago.
4:49:20This means that there is always something happening within the repo, setting up logging, writing tests, dockerizing the application, putting CICD pipelines in place. That way, every new feature you add automatically goes through all of these checks to make sure your application stays solid.
4:49:36So let's get that in place right now. Since in the crash course part of this course, we have gone through how CICD pipelines work through a hands on demo, this time, we'll approach it a bit differently.
4:49:49Instead of implementing it yourself, we'll have an AI agent set up pipelines by clearly specifying what needs to be done. So in the video kit down below, you can find this new prompt.
4:49:59You can copy it and paste it right here. That's the mindset you should start adopting for application development. You focus on the architecture and let AI handle the implementation.
4:50:09In this case, we're asking you to study the code base and create three GitHub action workflows. One for linting and formatting, another for testing, and a third one for Docker build and push.
4:50:21So press enter, and let's warp do its thing. It's asking us for permission to create a new repo called workflows. For sure, we can allow it to do that.
4:50:30And then it'll start creating these three workflows. I'll actually turn on the auto approval because I believe it should be able to do it properly. And there we go.
4:50:38In less than a minute, it created all these three GitHub action workflows. One for linting, which will trigger on pushes to main and staging branches, running tests, which will also trigger on pushes to main and staging, and finally, Docker pushes. Now it told us that we need some secrets to make sure these actually run.
4:50:57We need a Docker username, a Docker password, and a test database URL. So let me show you how we can get those.
4:51:04Head over to your browser and head over to Docker Hub. Then on the left side, you should be able to see some settings. Head over to personal access tokens and generate a new token.
4:51:18You can give it a description, something like JSM acquisitions and generate it.
4:51:23Now while keeping this page open, head over to your GitHub repo. And once you're there, go ahead and open up the settings.
4:51:32Then scroll down under security, secrets and variables, and search for actions.
4:51:38Here, we'll need to add repository secrets. So click new repository secret and give it a name of docker underscore username.
4:51:49Then back within our application, you can find your username right here at the top and simply paste it right here. Make sure there's no extra spaces. Let's repeat the same thing for the docker password.
4:52:01So say Docker underscore password. And for this one, I will copy this password right here and paste it there.
4:52:10Let's add two more. We also need to add our node environment. In this case, we'll set it to production.
4:52:16Finally. Right? And finally, the last one is our database URL.
4:52:21So just create it. Call it database underscore URL.
4:52:25And I believe that within our original dot ENV file, there should be this entire database URL. So simply copy it and paste it right here. Now back within our editor, you can see this new dot GitHub folder with a workflows folder inside of it and three new actions, docker build and push, tests, and linten format.
4:52:47They're all following a proper YAML configuration, and they would take us quite some time to write on our own. But, thankfully, it's much easier using AI once you know what you want it to do.
4:52:58So let's test it out. The only thing we have to do is just make a push over to the main branch. I'll do that right here by running git add dot git commit dash m implement c I c d pipelines and GitHub actions and git push.
4:53:15As soon as you do this, head over to your repo and back over to the actions. And in a matter of seconds, you'll see that a new workflow has been queued. So one by one, they will now run.
4:53:27Linting and formatting, testing, and then finally, Docker build and push.
4:53:31Linting failed, which is completely expected as we haven't properly linted all of our files, so it's possible that we're missing a couple of semicolons. And the fact that we have just 10 different errors is super good across all the files. So you can easily go ahead and make those fixes.
4:53:47And if you click over on lint and format check, you can see exactly what it did. So it ran the ESLint dot command, and then it figured out exactly where those issues are.
4:53:59Now you can totally auto fix those as well and then push again. Our second action succeeded, and this one was running our tests. Remember that test dot YAML file that we created not that long ago?
4:54:10Well, thankfully, all of those tests have passed. And at the bottom, you can even see a new artifact generated through this workflow run. It's a full coverage report which you can click on, and then it downloads it, and then you can pull the index HTML to the browser so you can see the full coverage report.
4:54:28Pretty cool stuff. Right? And finally, there's the Docker build and push.
4:54:32We could inspect further why this Docker image failed, but this is a perfect chance to take a look at the file itself and maybe do some debugging. That's the point of working with AI. Sometimes, like your coworkers, it'll ship broken code.
4:54:44But if you're good enough, you can fix it. Let's see. Oh, take a look.
4:54:50Here, we have our username, which is good, but the image name is acquisitions. That's not the image name we've been using so far. If you head over to Docker, you'll know that we use Kubernetes demo API as the image name.
4:55:03So simply replace this one with that one and save it. And while we're here, let's also lint our repo by running npm run lint.
4:55:13I believe that's the command that we had right here. And this will give you all the issues that we have to fix. Then which other commands do we have within package JSON?
4:55:23It was lint fix. Yep. This is exactly what I wanted.
4:55:27So now we can run n p m run lint fix, and this should auto fix most of these issues. So if you once again run n p m run lint, you'll see that now we have no issues.
4:55:39And finally, we can run NPM run format to do prettier formatting as well. Beautiful.
4:55:46And now we can do another push and see what our GitHub actions say. Git add dot git commit dash m, fix Docker GitHub action, and linting, and git push.
4:55:59As soon as you do this and come back to your actions, you'll see that three new actions will instantly be queued running one after another. So let's wait a minute, and let's see what they have to say.
4:56:11And what do we have? Regarding the linting, it failed again, but this time, ESLint is good. It's just saying that maybe there's a bit of a formatting issue with Prettier, but I'm totally okay with that for now.
4:56:24The other thing I'm more concerned about is Docker build and push failing. And if we look into it, you can see that it's once again complaining about some OAuth token permissions. Unauthorized access token has insufficient scopes.
4:56:39Okay. So this makes me think that this token we created doesn't have the necessary permissions to do what it needs to do. So let's go ahead and create a new one, but this time, we won't make it read only.
4:56:50We'll give it write permissions. So I'll call it GSM acquisitions token, and this time, I will make it read, write, and delete.
4:57:02And we'll have to copy this password, head over into the settings of this repo under secrets and variables and actions, and then modify the Docker password, which is more or less the Docker token, and then you can paste it right here.
4:57:18It's asking me to verify, and it got updated. So third time's the charm. Let's do another push.
4:57:24This time, we don't even have to do it from the code because we didn't change anything. Rather, we can change a read me, which will trigger another push. I'll say testing c I c d pipelines and commit and push.
4:57:39Now under actions, you'll see that all three will be retriggered, and this time, we're hoping for two out of three. And there we go.
4:57:47You can see that all the steps of the build and push Docker image action have been completed, and this time, it is green. Now why did we even create this action in the first place?
4:57:59Well, we did it so that whenever you make any changes to your code base, we automatically regenerate and repush a new Docker image. So when you decide to add Kubernetes to this project, your Kubernetes clusters will always be pointing to the right versions of the code. So with that in mind, you've successfully added CICD pipelines and GitHub actions to this DevOps acquisitions API.
4:58:22Great work. So what's next? Well, so far, we've implemented the controllers, routes, and middlewares for authentication and for the users.
4:58:34Not yet an acquisitions application, right, where people can buy some SaaS businesses and sell them and so on, but now it's all about repeating the same process you followed so far. Create the listings, their model, endpoints, let Warp generate the rest, test it, and then do the same for the deals.
4:58:52Check if your pipelines are running smoothly, make adjustments, test it again, and keep iterating.
4:58:59Somewhere along the way, after completing your first MVP, try deploying with Kubernetes locally as I showed you during the crash course part of this course. Then once you're comfortable doing that, pick a cloud provider and replicate the same setup using their clusters instead of Minikube.
4:59:16If you want a full final code base with deployments, click the link down in the description. It'll give you more info, most likely pointing to jsmastery.com where I'll guide you through the rest of this course in detail.
4:59:28And for an even deeper dive, including self hosting Postgres, learning cloud providers like AWS, deploying dockerized applications, building advanced pipelines, setting up notifications, and more, the ultimate back end course, which is not here yet but is coming very, very soon, is exactly what you need.
4:59:45I'll link the waitlist down in the description so you can join and know as soon as it's out. For now, I hope this video helped you understand what DevOps is and gave you the strategies you can apply to your own projects to impress recruiters and land that job.
5:00:00So with that in mind, thank you so much for watching, and I'll see you in the next one. Have a wonderful day.
The Hook

The bait, then the rug-pull.

DevOps gets introduced as a gatekeeping monster — the word that makes frontend developers feel like they don't belong. The first 82 seconds dismantle that framing, name every tool in the course, and promise a single, coherent project that connects them all.

Frameworks

Named ideas worth stealing.

09:45model

DevOps Infinity Loop

  1. Plan
  2. Code
  3. Build
  4. Test
  5. Release
  6. Deploy
  7. Operate
  8. Monitor

The eight-stage lifecycle that defines what DevOps automates — from planning through monitoring and back again.

Steal forExplaining to clients or teammates why manual deployments are a bottleneck
1:04:24concept

Docker Lunchbox Analogy

A container is a lunchbox that packs not just the main dish (your code) but all the specific ingredients (dependencies, runtime, config) — so it tastes the same no matter whose kitchen you eat it in.

Steal forExplaining Docker to non-technical stakeholders or in course content
50:33list

CI/CD Four Stages

  1. Build
  2. Test
  3. Deploy
  4. Monitor

Every CI/CD pipeline reduces to these four gates — anything more elaborate is a variation on this core sequence.

Steal forPipeline design or course structuring
CTA Breakdown

How they asked for the click.

VERBAL ASK
4:48:35next-video
For an even deeper dive, including self-hosting Postgres, learning cloud providers like AWS, deploying dockerized applications, building advanced pipelines, setting up notifications, and more, the ultimate back end course is exactly what you need.

Soft close — points to a waitlist for an upcoming Backend Pro course rather than a hard paid pitch. Low friction, high goodwill.

Storyboard

Visual structure at a glance.

open
hookopen00:00
what is devops
valuewhat is devops09:45
docker crash course
valuedocker crash course1:03:21
building real projects
pivotbuilding real projects2:30:49
dockerization
valuedockerization4:03:11
cicd finalization
ctacicd finalization4:48:35
Frame Gallery

Visual moments.

Watch next

More from this channel + related breakdowns.

Chat about this