Docker Tutorial for Beginners — Transcript
Full transcript
- 0:00If you ask me to name one skill that is
- 0:02common among software engineers, AI
- 0:03engineers and field deployed engineers,
- 0:05then that skill is Docker. As per Stack
- 0:08Overflow developer survey 2026, Docker
- 0:11is at number one position in cloud
- 0:14category. So clearly it is most required
- 0:17skill by the developer.
- 0:20Even this little guy agrees. He's 50
- 0:23years old developer and he has seen a
- 0:25lot of tools come and go. In this
- 0:28beginner friendly tutorial, we will
- 0:30learn Docker through simple explanations
- 0:32and hands-on practice. We will start
- 0:34with Docker fundamentals. We'll run
- 0:36Docker commands. Then we'll create a
- 0:39container for a simple Python
- 0:40application and at the end we will have
- 0:43a complete rag application that we are
- 0:46going to dockerize. So if you are
- 0:48planning to become AI engineer or if
- 0:50you're a software engineer leveling up
- 0:51your game, you will understand some
- 0:54serious docker fundamentals towards the
- 0:57end. We will also provide you the PDF
- 1:00cheat sheet and some useful Docker
- 1:02resources. Joining with me is Sidan who
- 1:04is a Google developer expert and AI
- 1:06engineer who has been using Docker for
- 1:08more than 5 years in product based
- 1:10companies. He also teaches with me in
- 1:12our AI engineering cohort.
- 1:15And of course, we have baby Yoda who
- 1:17will be supervising all of us.
- 1:20And how about we introduce a story of
- 1:23Atlake Technologies where Peter has
- 1:25built a rag chatboard. Bruce wants
- 1:27client demo. And things are about to get
- 1:30interesting.
- 1:32It is 11:47 at night, the night before
- 1:35the big demo. Bruce, the CEO, has just
- 1:38one question. Does it run? And Peter
- 1:41says yes, but on his machine. So let's
- 1:44rewind a bit. Meet Peter Pande. He's an
- 1:47engineer at Atlake Technologies and he
- 1:50has built a rag chatboard for a telecom
- 1:52client. Data plans, coverage, customer
- 1:55policies, you can ask it anything. So
- 1:58Bruce does, does the unlimited plan
- 2:00include international roaming and it
- 2:03nails it. Now Bruce wants the same demo
- 2:06running on the client server by
- 2:08tomorrow. Peter is thinking, how hard
- 2:11can it be? He sends the code and the
- 2:14requirements file over to Miles Mishra,
- 2:16the deployment guy, and that's it. Done.
- 2:20Or so he thinks, because the app won't
- 2:24even start. First, it's the wrong Python
- 2:27version. They fix that. Then there is a
- 2:29system library Peter installed weeks ago
- 2:32and completely forgot about it. So, they
- 2:35fixed that, too. And the config file, it
- 2:38is still sitting on Peter's laptop,
- 2:40relaxing. And that's how we got here.
- 2:44Peter says the line, "Every engineer has
- 2:46said it at least once. It works on my
- 2:50machine." And Bruce has a plan. Ship the
- 2:52laptop to every client. That's when Tony
- 2:55Sharma, the engineering leader, walks in
- 2:58with his chai and spots the problem
- 3:01right away. Peter sent the rice, but he
- 3:04forgot the masala. The code is just the
- 3:06rice. The app also needs the right
- 3:09Python version, the installed libraries,
- 3:11the system dependencies, and the
- 3:14configuration. And none of that made the
- 3:16trip. Then Miles has an idea. What if
- 3:19they pack the app with everything it
- 3:21needs? The right Python, the packages,
- 3:24the system libraries, all in one box.
- 3:26You build it once and you reuse it. No
- 3:29more rebuilding environments by hand. So
- 3:32how do you actually build that box?
- 3:34Docker. Tony's plan is simple. Build the
- 3:37image, supply the config, and test it on
- 3:40the deployment machine. All before the
- 3:42demo. But Peter wants to understand it
- 3:44first. So Miles starts small with a
- 3:47simple fast API app. So let's join them,
- 3:51build our first Docker image, and work
- 3:53our way up to the Telecom chatboard.
- 3:59This one meme summarizes the entire
- 4:02story really well. This is how Docker
- 4:04was born. This reminds me of my time at
- 4:07Bloomberg where we used to face one
- 4:09particular problem. The problem was
- 4:11something like this. We will have one
- 4:13team, let's say earning estimates team
- 4:15where I was working and we'll have a
- 4:17developer moan who will be working on
- 4:19program A. Let's say calculating
- 4:22consensus estimates and that program
- 4:25needs pandas 2.0 and let's say MongoDB
- 4:298.0. So he will test these things out on
- 4:32his dev machine. He will install
- 4:33everything, make sure things are looking
- 4:35good and when everything is good, he
- 4:39will copy this program to a production
- 4:42machine. So let's say this is our
- 4:43production machine. We used to have this
- 4:45machine called GU generic server and
- 4:48that will have global packages. Okay. So
- 4:50let's say this has pandas 2.0, MongoDB
- 4:548.0, moan's program is working. Okay.
- 4:57Now you can have another team. Let's say
- 5:00a developer called Rita. She's having
- 5:03this program B and she needs same kind
- 5:06of packages and dependencies. So now she
- 5:09will copy her program B to this
- 5:12production server. Now both of these
- 5:14programs are using global packages which
- 5:16are installed on this production machine
- 5:18and things are working okay. But at some
- 5:20point let's say Arita who is let's say
- 5:23in a equity team she's writing this
- 5:25total return calculation program and to
- 5:28speed things up or for something else
- 5:30she needs MongoDB 9.0 version. So now
- 5:33she will go and update let's say pandas
- 5:363.0 zero, right? She will update these
- 5:38global packages and many times it will
- 5:40break program A. This was a recurring
- 5:43theme. To solve this problem, we created
- 5:46a docker-l like system basically which
- 5:49is a container. And here you put this
- 5:53entire program and all its dependencies
- 5:57into a container. Okay? And then you
- 6:01move this entire container here. So you
- 6:05move let's say this entire container
- 6:08here and then Rita will do the same
- 6:11thing. So Rita has her program B okay
- 6:15and her program needs pandas 3.0
- 6:19and MongoDB 9.0 zero. She has her own
- 6:23container. We used to call this B
- 6:25package Bloomberg package but the idea
- 6:27is same as container and they're using
- 6:30the same operating system but the
- 6:32dependencies are now contained into that
- 6:35particular package and that solve many
- 6:38problems because things are more
- 6:39reproducible more predictable this way.
- 6:45Now for anyone learning docker a natural
- 6:50question arises what is the difference
- 6:51between container virtual environment
- 6:53and virtual machine. Those who have
- 6:56worked in python know about python
- 6:59virtual environment. Here what we do is
- 7:04we uh package all the dependencies for
- 7:08your program. So let's say my program
- 7:10needs pandas fast API all these packages
- 7:14you can put all of them in a common
- 7:18virtual environment
- 7:20and then the Python installation the
- 7:24actual installation you are using it
- 7:25from your host installation so this is a
- 7:28computer on which let's say I have
- 7:30installed Python let's say 310
- 7:34okay so then in my virtual environment
- 7:37this Python executable is a sim link.
- 7:40It's just a link or maybe a lightweight
- 7:43copy if it is Windows. But if you want
- 7:46to use 3.14 in your virtual environment,
- 7:49you can't use it. You are still reliant
- 7:51on this. Okay. So the host operating
- 7:54system is still same. You can have three
- 7:56different program running with three
- 7:58different virtual environment. So Sidan,
- 8:00do you think this can solve the problem
- 8:02that we were facing earlier?
- 8:05only partially because this will only
- 8:07isolate the environment that is related
- 8:09to Python. But what about other
- 8:11packages? Let's say what about other
- 8:14dependencies like let's say radius or we
- 8:17saw in your previous example. What
- 8:19about those? We still need to isolate
- 8:21them.
- 8:22>> So this won't work.
- 8:24>> Okay. And another one I can think about
- 8:26is the Python, right? So let's say one
- 8:28program needs Python 3.10, the other one
- 8:31needs Python 3.14. virtual environment
- 8:34doesn't solve that problem. So for that
- 8:38maybe we can use a different system
- 8:41called virtual machine.
- 8:44Here you have your host operating system
- 8:47on which you can have this hypervisor
- 8:50layer on top of which you can create an
- 8:54entire
- 8:56machine basically. So this is a virtual
- 8:59Linux machine with its own OS. So watch
- 9:02this.
- 9:03So you have your own OS, you have your
- 9:05own Python packages. If you want to have
- 9:08your own radius, here you can have your
- 9:10own radius, MongoDB, everything. And
- 9:13here you have app code. So this entire
- 9:16machine is kind of encapsulated. And if
- 9:20you want to have a different machine,
- 9:21let's say Windows machine, you can have
- 9:23that too, right? Because here it is
- 9:26running Windows C OS. Okay? And this is
- 9:29the approach that many cloud providers
- 9:31such as AWS follows, right? They have a
- 9:34one big machine on which they will spin
- 9:36off their virtual machines. So now
- 9:40our problem of separate Python
- 9:43installation, you know, RadiusB having
- 9:45different versions all of that is
- 9:47solved. So SAN, what do you think about
- 9:49this? I think we are going way overboard
- 9:52here because I had to ship my
- 9:55application code with its dependencies.
- 9:58But now it's like I'm taking my whole
- 10:00computer instead of just the app and the
- 10:03dependencies and pushing it somewhere on
- 10:06the cloud. Now this is going to take a
- 10:08lot of time to build. That is one thing.
- 10:11Other thing is this is also very
- 10:12resource intensive because now you have
- 10:14a separate operating system that needs
- 10:17to run just for your application. Don't
- 10:19you think it's like too much?
- 10:21>> Yeah, this sounds like too much. This
- 10:25more sounds more like I have a very
- 10:26small table and I am using a truck to
- 10:30move that table, right? It's too much.
- 10:33The size of this virtual machines can be
- 10:36in gigabytes. They are very uh resource
- 10:40intensive. Let's say you want to upgrade
- 10:42your OS. Now, you have to go to these
- 10:44machines and uh go through all that
- 10:46upgrade uh process and so on. Okay. So,
- 10:49so you are absolutely right. This is too
- 10:51much. So, then let's go to the next
- 10:55solution which is basically Docker.
- 10:57Okay. So, in Docker we have shared host
- 11:02basically shared host kernel. So,
- 11:04operating system is common. On top of
- 11:08that you have something called a Docker
- 11:11engine and on top of that you have a
- 11:13Docker container. In this container, you
- 11:17have application code, you have your
- 11:19dependencies,
- 11:21you have your own Python installation.
- 11:23Okay? So, if I need 3.10, 3.14, I can
- 11:27have it in my own container. I'm not
- 11:30referring it to some global installation
- 11:32on my host kernel. I also have system
- 11:35libraries, CUDA, lip, pq, whatever
- 11:38system libraries that you need. Okay.
- 11:41And then if I need MongoDB or radius, I
- 11:45have separate containers for that. Okay.
- 11:48So radius is its own container. MongoDB
- 11:50is it own container. And that way this
- 11:53entire system can
- 11:56package my dependencies into one unit.
- 12:00At the same time, it keeps things
- 12:02lightweight. So what do you think about
- 12:04this?
- 12:05>> Yeah, this looks like a good solution.
- 12:08>> Yeah. So now instead of using a truck I
- 12:12am using my car to move a small table
- 12:15that I have. Okay. So this particular
- 12:17diagram
- 12:19compares the three approaches. So as we
- 12:22saw virtual environment is only
- 12:26dependencies. Containers are
- 12:28dependencies plus system libraries plus
- 12:32uh other components in your system. You
- 12:35know radius, MongoDB, whatever you need
- 12:37it.
- 12:38Whereas virtual machine is an entire
- 12:41operating system with all the bells and
- 12:44whistles. If I have to use the analogy,
- 12:47here's the analogy. Virtual environment
- 12:49is like a room. Container is like a flat
- 12:52and virtual machine is a house.
- 12:58Enough of the talk folks. Let's install
- 13:01Docker. Now before installing Docker,
- 13:04you need Windows Subsystem for Linux. If
- 13:07you are using Windows, I'm on Windows so
- 13:10I have to install this. If you are on
- 13:12Linux, then it already has Linux
- 13:15operating system. So you don't need
- 13:17this. Okay. So for Windows, what you do
- 13:20is you open PowerShell, which is like
- 13:22the command prompt essentially. And here
- 13:26you just run WSL install. Okay? And that
- 13:31will install it. Now in my case, of
- 13:33course, I have already installed this so
- 13:35it's not going to work. But if you have
- 13:37not installed this then it will go ahead
- 13:40and install it. So Windows Subsystem for
- 13:42Linux is basically a way to run Linux
- 13:45operating system on top of Windows
- 13:48because Docker runs on Linux. Okay,
- 13:50that's why we need this. Now install
- 13:52Docker in Google. You will say install
- 13:54Docker desktop. You go to this
- 13:57particular link. You see Docker desktop
- 14:00setup installed. You choose your
- 14:01operating system and then in my case
- 14:04it's Windows. So I will click on this.
- 14:07It will download it. I have already
- 14:09downloaded it. Okay. So what you do is
- 14:13just double click and follow common
- 14:16sense folks. We live in the era of AI.
- 14:19If you're stuck, ask your friend chat
- 14:22GPT or Claude and just get it done.
- 14:25Folks, you don't need to be spoonfed for
- 14:27this. Once Docker is installed, you will
- 14:30run a command prompt and verify it is
- 14:34installed. So you can just run docker
- 14:37version and see this means that my
- 14:41docker is installed.
- 14:46Now that my installation looks good, how
- 14:48about we download the docker image for
- 14:52python and we try it out. So far you
- 14:55might have tried Python by installing it
- 14:57locally but now we will try it through
- 15:01docker. So let's see how that looks. Now
- 15:04from where we are going to download it
- 15:06actually there is this website called
- 15:09hub.docker.com docker.com.
- 15:11This is like a git repository of all the
- 15:13Docker images. And if you search here
- 15:17for Python,
- 15:19I will see Python's official repository.
- 15:24Okay. So see Docker's official image 1
- 15:27billion plus downloads. So I'm going to
- 15:30download uh some image. So let's see. So
- 15:33it has all these tags. So tags are
- 15:36basically versions of the Docker image.
- 15:38Okay. And docker image is more like a
- 15:40class. So you download the image on the
- 15:42disk. And when you start the container
- 15:45that is called container. It is like an
- 15:47object of the class. So image is a
- 15:49blueprint. It's a class and container is
- 15:52an object or instance of that class.
- 15:55Okay. So here I see all these tags.
- 15:58Okay. So maybe let me see which one I
- 16:02want to download. So for Python,
- 16:05how about I download let's say this one
- 16:093.13
- 16:11slim. Okay. So this is the image that I
- 16:15want to download and the command for
- 16:18that is going to be docker pull. Okay.
- 16:22So docker pull and then
- 16:25what do you want to download? So python
- 16:27image. Okay. And then 3.13
- 16:31slim. So we'll go over what slim means
- 16:33but basically you are downloading 3.13
- 16:36version of Python image
- 16:39and it will now via internet it will
- 16:42download it locally on your computer.
- 16:45See it is just pulling this thing. So
- 16:47now it is available in your local
- 16:49docker. But how do I see it exactly?
- 16:53Well you can just run docker images
- 16:56command and in this images. So let me
- 17:00just make it clear. You see I'm seeing
- 17:03this Python's uh slim version. So slim
- 17:06version is a little bit trim down
- 17:08version. If you download a full Python
- 17:10image with all the binaries etc. it it
- 17:13it's going to be big you know 1 GB
- 17:15something like that very big so I want a
- 17:18lighter version of Python. So that's why
- 17:20I have downloaded slim which is 181 MGB.
- 17:24So these are all the images that I have.
- 17:27Now let's run this Python using this
- 17:30docker image. So you will use this
- 17:32command called docker run. Okay. So
- 17:36docker run and then name of the image
- 17:403.13
- 17:41slim.
- 17:43Now see when you run it like this
- 17:45nothing is happening because it is
- 17:46starting Python and it is exiting
- 17:49immediately. So if you want it to run in
- 17:52interactive mode you have to say it, you
- 17:55know. So this is interactive mode and
- 17:57now see I'm running my Python code. So
- 18:00here I can write all my Python code.
- 18:03This interpreter is essentially coming
- 18:06from this particular docker image that
- 18:09we just ran. Other than the command line
- 18:12interface you can access all of this via
- 18:15user interface as well.
- 18:18For that here you can type docker
- 18:22desktop. So docker desktop is the
- 18:24application that we installed and here
- 18:27you will see all these images. You see
- 18:30this image 3.13 slim. This is what you
- 18:34downloaded and if you want to run it you
- 18:37can click run here and it will run it or
- 18:40see in the container I have that image
- 18:43running here. See you can stop it from
- 18:45here as well. You can start and stop it
- 18:47from here too. But see here if I start
- 18:50it will start and then stop because we
- 18:53are not running it in the interactive
- 18:55mode. Okay, this needs to run in the
- 18:58interactive mode. So this is a simple
- 18:59demonstration of how you can download
- 19:03Python's image and run it. Now let's
- 19:06download a different image. How about
- 19:08So docker pull Okay. So
- 19:12here if I go to the website
- 19:17and if I say you will find this
- 19:24uh image. Okay. And in that there is
- 19:28this tag called latest. Latest refers to
- 19:31the latest version of this image. Right.
- 19:34So we are going to just download latest.
- 19:36If you don't specify anything it will
- 19:38download latest. So let's do this. So I
- 19:41will say docker pull
- 19:44And by the way this is going to be a bit
- 19:47of a bigger image. It's 1 GB. So if you
- 19:50don't have that much space on your
- 19:51computer then you can just watch it. I
- 19:53mean we are not going to actively use
- 19:54MongoDB later on. I I just want to show
- 19:57how you download the bigger images and
- 20:00how you play with it. Okay. All right.
- 20:02So this image is downloaded. Now when I
- 20:04run docker images I see two images. You
- 20:08see I see one is another one is
- 20:13python. Okay. Now let's run this
- 20:18image. So for this you can just say
- 20:21docker run Simple. Whatever image
- 20:24you downloaded you just say docker run
- 20:26and it will run that. It will run
- 20:30that. Meaning it is starting
- 20:33database. Now see my database has
- 20:35started. But one thing I'm noticing is
- 20:37it has blocked my terminal. See now I'm
- 20:40not able to type any other command. So I
- 20:43will exit out of this and what I will do
- 20:45instead is I will run same command but I
- 20:48will say
- 20:50hyphen d which means detached mode.
- 20:53Detach mode means it's now running it in
- 20:55the background. Okay. And I can now work
- 21:00in my command line. But how do I see all
- 21:02the processes that we are running? file
- 21:04you just say docker ps. Okay, so that
- 21:08will show you
- 21:10see this is like a Linux command write
- 21:13processes and it will show you the
- 21:14process and the name of the process is
- 21:17objective query. It is just giving this
- 21:20fancy name but it is essentially uh your
- 21:23container that is running. So
- 21:26hopefully now you're understanding
- 21:27difference between the images and the
- 21:30container. Image is something like a
- 21:32class. It is on a disk and container is
- 21:35like a running process. It is running
- 21:37right now. Okay. And you can see the
- 21:39same here also. See images. I have two
- 21:43Python and And container I have
- 21:47multiple. See I I I ran all this
- 21:49container before. Let me just delete
- 21:51this. I I'm just deleting the container.
- 21:53Okay. I'm not deleting the image. So
- 21:56image wise only is running. uh
- 21:59sorry container wise only is
- 22:02running and image wise I have two uh
- 22:05particular images okay one good practice
- 22:09is to start uh this container with a
- 22:13specific name so I can say something
- 22:15like docker
- 22:18minus d see let's use the same command
- 22:20right so this is the command that you
- 22:21use you can give name here you can say
- 22:24my okay so when you do that What
- 22:28is happening actually is now we are
- 22:31running two different containers. So
- 22:34image is same it's but you are
- 22:37running two containers one is my
- 22:40the first one is objective query that's
- 22:42a default name that it gave and if you
- 22:45look at container here you see these are
- 22:48the two see two okay so let me uh delete
- 22:53this objective query I don't need this
- 22:55so I have only one container running my
- 22:58okay images are still two now if
- 23:01you want to stop my what you and
- 23:04say is docker stop my Okay, you
- 23:08have to give this name because can
- 23:11be running uh as multiple instances. You
- 23:14want to stop this particular instance.
- 23:16So you are stopping that container. And
- 23:20now when you do ps see that has
- 23:22disappeared. This one we deleted from
- 23:24the UI. Okay. And if you want to start
- 23:26that again you can say docker start my
- 23:30So now it has started it. Okay.
- 23:34So first time when you ran it, you ran
- 23:37it through run command. You gave it a
- 23:40name. When you do stop my it will
- 23:42stop it. But that my name is still
- 23:44registered. So when you say start again,
- 23:47it knows what to start. Okay. So here
- 23:52it is my And now let me start a
- 23:55different one. I will use the run
- 23:57command. And instead of my I'll
- 23:59say your Okay. My Your
- 24:00And then now we have two of them
- 24:04running right. So two processes.
- 24:07One is my another one is your
- 24:09This is a learning tutorial. But
- 24:12in real life, you might want to start
- 24:14two instances with two different
- 24:16authentications or let's say two
- 24:19different databases really. You know you
- 24:21have one dev database, one staging
- 24:23database. You can start multiple
- 24:26databases and all these databases have
- 24:30particular container ID. You see this is
- 24:32the container ID and the image is same
- 24:35again image is same but these two
- 24:38are two different containers you know
- 24:41two different docker containers that you
- 24:43are running and here it is showing you
- 24:45the status this is up this is up here it
- 24:48is showing you the port name is your
- 24:50my now I can say docker
- 24:55stop my okay so I'm stopping my
- 24:57your is still running and
- 25:00when you Say docker ps. Now it is
- 25:02showing only your But if you say
- 25:06same command with hyphen a it will show
- 25:10you my as well. See and in my
- 25:14it will say exited 12 second ago.
- 25:18So let's understand the difference. When
- 25:20you say run, you are starting a
- 25:23container with that name for the first
- 25:26time. when you say stop you are stopping
- 25:30that particular container but this name
- 25:32is still in the memory so that if you
- 25:35want to start again I can say docker
- 25:38start my and I'll be able to start
- 25:40it
- 25:41and p-en a is the command that you use
- 25:45to see all those container instances
- 25:49with their names so that if you want to
- 25:51start and stop you can do it so okay so
- 25:54right now if I want to start docker
- 25:56start my it will work because the
- 26:00name is in the registry but let's say if
- 26:03I want to say docker start yoda
- 26:07then it will not work because that name
- 26:10is not registered basically it's not in
- 26:13the list of known docker containers
- 26:16already so this one you have to run you
- 26:20have to just say docker run minusd
- 26:24minus name yoda
- 26:26then only later on you can do start and
- 26:29stop. Without that you can't do it. So I
- 26:31hope you understand it. Many people get
- 26:33confused between run and start and stop
- 26:35commands. Okay. Now we have see if you
- 26:40run this command ps a we have two
- 26:44containers. One is stopped already one
- 26:47is still running. Now let's say my
- 26:50container I want to remove from this
- 26:52list. For that you can simply say docker
- 26:56rm my
- 26:59and that will remove that from the list
- 27:02so that when you now
- 27:06type this it does not show up. So now
- 27:10when I say docker start my it's
- 27:13not going to work because it's not in
- 27:15the list of known names. All right. Now
- 27:18how do you remove the image? Let's say I
- 27:20want to remove the MongoDB image
- 27:23completely. Well, there are two ways.
- 27:26One way is through the UI. You go to
- 27:28images. You see this nice little delete
- 27:32icon. It say this image is going to be
- 27:35deleted. Or you say docker rm image. See
- 27:40there was rm command to remove the
- 27:42docker container. rm i. I stand for
- 27:45image. Remove image. Okay. and
- 27:50conflict unable to delete must be
- 27:54forced. So this other container is still
- 27:56running right. So we have to stop it. So
- 27:58I will say docker stop your
- 28:02because your is still running. And
- 28:04then uh I have nothing running. Okay.
- 28:08Okay. Let me just remove it. So let me
- 28:10do a complete cleanup. So docker rm
- 28:13yarmong.
- 28:17And now I have nothing running and I can
- 28:20say docker
- 28:23rm images okay
- 28:27and that has deleted it. So now when I
- 28:29say images I don't see it and from the
- 28:32UI also it is gone. If you have let's
- 28:36say 10 images you don't want to delete
- 28:38them step by step. You want to delete
- 28:40everything. In that case you can say
- 28:42docker system prune a. Be very careful
- 28:46when you are using this command because
- 28:48this is like delete all. Okay, delete
- 28:50star and that's it. See now when I say
- 28:54docker images I don't see anything. My
- 28:57docker is basically now blank. See I
- 28:59don't have anything.
- 29:03>> Let's now talk about docker
- 29:04architecture. It's a client server
- 29:07architecture where you have a docker
- 29:10client. So whatever commands that we
- 29:12were running in the command prompt that
- 29:13is essentially your docker client. It is
- 29:17a lightweight CLI function which you use
- 29:21it to send commands to the server. Okay.
- 29:24The server is actually the docker back
- 29:27end. It's called docker demon. The
- 29:30process name is docker d. Docker demon.
- 29:33D stands for demon. And through rest API
- 29:37the client will send commands to docker
- 29:39demon. Now docker demon is the program
- 29:42that creates and runs the containers. If
- 29:46you are creating your own images you
- 29:49know building new images which we'll see
- 29:51in a bit that is also done by docker
- 29:54demon. It's essentially the docker back
- 29:56end. Basically client is just
- 29:58lightweight. whatever commands we are
- 30:00just running that is a client basically
- 30:02and behind the scene it is sending this
- 30:05rest api commands to the demon okay and
- 30:10then when you say something like docker
- 30:12pull it is getting all these images from
- 30:16the docker registry which is on the
- 30:18cloud so that docker hub that we saw
- 30:20which has so many different images for
- 30:23different kind of softwares and such
- 30:25that is basically your cloud repository
- 30:28it's GitHub from where you are pulling
- 30:31your Docker image locally and Docker
- 30:34engine is nothing but the demon rest API
- 30:37and CLI. It is essentially a package
- 30:40which includes all these components. If
- 30:42you are looking at your Windows task
- 30:44manager
- 30:46uh here you will find Docker Desktop,
- 30:50right? So this is essentially the client
- 30:53and the back end actually is your docker
- 30:58demon process is running inside the WSL
- 31:02which is Windows subsystem for Linux.
- 31:05Docker D process is something we won't
- 31:07be able to see because it is running
- 31:09inside that WSL. But if you are running
- 31:13docker on Linux then you will be able to
- 31:16see this process. Okay. So just say ps
- 31:19and try to find this process docker d if
- 31:22you're running Linux on windows just
- 31:25know that docker d is running inside
- 31:28this uh windows subsystem for linux.
- 31:34Now let's go over docker hub very
- 31:36quickly. You can use some other
- 31:38repository as well but this is the
- 31:41official hub with many images which are
- 31:44available. Now when you search for any
- 31:47image let's say postgrade right so here
- 31:50you are finding this postgrace official
- 31:53image so there is this tag called
- 31:55official image and there are other tags
- 31:58too so if you explore here
- 32:01on the left hand side you will see
- 32:03docker official image harden image and
- 32:05so on okay so for postgress the thing
- 32:09that we saw so let me just show you so
- 32:11this posgress right this one you see
- 32:15this gray in badge docker official
- 32:17image. So this is an official image
- 32:19which has been approved by a docker
- 32:21community. Okay, but this image may have
- 32:26vulnerabilities. So look at this. It has
- 32:29some 31 high severity vulnerabilities.
- 32:33Okay, look at this. You see this?
- 32:36Although it's an official image but many
- 32:39people use this. You see 1 billion plus
- 32:41downloads because this is a complete
- 32:43package. It comes with all the bells and
- 32:45whistles, all the tools. Okay. The other
- 32:48option that you have is let's say I I
- 32:52click on docker harden image and when I
- 32:55look at this one, this one has almost
- 32:58like zero critical severity. Okay. So
- 33:01what has happened is you take the
- 33:05official image basically okay or base
- 33:07post SQL image and you do a thorough
- 33:10security review. you remove unnecessary
- 33:13things. So many things are stripped off
- 33:16from the main actual image. So when you
- 33:20use this harden image, you get better
- 33:23security, you see, but you may not get
- 33:27all the tools, all the bells and
- 33:29whistles that you get with the official
- 33:31image. So there is a trade-off. What
- 33:34people do is when they want to use
- 33:36docker in the production you know people
- 33:39care about security they will use
- 33:40hardened image and then anything extra
- 33:44that you need you get it externally
- 33:45basically. Okay. So for production use
- 33:48cases people use harden image because
- 33:51vulnerabilities are lower and for
- 33:53development use cases they may use the
- 33:56official images. So just spend some time
- 33:59exploring this. The UI is pretty
- 34:01intuitive. any kind of package you want
- 34:03to search using categories you can
- 34:05search see I want to look at all the
- 34:07docker images for message cues and here
- 34:10is showing me kafka rabbit mq and so on
- 34:16the typical workflow for dockerizing any
- 34:18kind of application looks something like
- 34:20this let's say you have this app and
- 34:22this app could be written in any
- 34:24language Python Java NodeJS does not
- 34:27matter to dockerize this you first
- 34:29create a docker file that will contain
- 34:31in all the instructions that are
- 34:33required to package this app into a
- 34:36docker image. We will use this docker
- 34:38image to start up a container which is
- 34:40nothing but a special kind of process
- 34:42with its own file system. Our
- 34:44application will get loaded into this
- 34:47special container and it will execute in
- 34:50the isolated environment provided by the
- 34:52container and this docker image that we
- 34:54are talking about it will contain
- 34:56everything that is required to run our
- 34:58app. for example, slim operating system.
- 35:01At first glance, it might seem like
- 35:03Docker is building an entire OS. In
- 35:05reality, it doesn't. Each container
- 35:08includes just the user space files it
- 35:10needs and borrows the kernel from the
- 35:12host machine. It could contain the
- 35:14runtime dependencies of the language
- 35:16that you're running in. Could be Python,
- 35:18could be Java. It could also contain the
- 35:21third party applications that are
- 35:23required to run your app. the
- 35:25application files themselves like your
- 35:28py files, your java files that you have
- 35:30written as a programmer and any kind of
- 35:33environment variables or configurations
- 35:35that are required to execute your app.
- 35:38Let's see this practically by
- 35:40dockerizing our own application and
- 35:42learn docker by building a docker file
- 35:44ourselves.
- 35:45>> Let's get started. I'm in my
- 35:47anti-gravity editor and I've created a
- 35:49very simple fast API application. You
- 35:52can see this is a simple server with
- 35:55only two endpoints. One is the get
- 35:57homepage and the other one is get health
- 36:00status.
- 36:02And they respond with a very simple
- 36:03JSON.
- 36:05And they're using only two dependencies.
- 36:08One is fast API, the other one is UVCON.
- 36:12We are going to dockerize this
- 36:13application. Before we dockerize it, let
- 36:16me run it and show it to you. Now we
- 36:19need to remember and keep track of all
- 36:21the steps that we are following to start
- 36:23up our app. Here the first thing that I
- 36:25need to do is I need to go into this
- 36:29particular directory. So I will cd into
- 36:33hello API and then I need to install all
- 36:37the dependencies that are present in
- 36:40this requirements.txt file. For that I
- 36:43can use pip. So I'm going to write pip
- 36:48install -ash r. I can specify the
- 36:51requirements file.
- 36:53This will install all the dependencies
- 36:55like fast API uon everything. My
- 36:58dependencies are installed
- 37:00and then I can use uvicon to run this
- 37:04particular server.
- 37:06So, uicorn
- 37:09and main which is my file name main
- 37:12colon app and then I can specify the
- 37:16port where I want to start up the
- 37:18server. So port typically it is 8,000.
- 37:24When I run this you can see my server is
- 37:26started. I can go to this link and you
- 37:31see in the homepage I'm getting this
- 37:33hello from inside a container. It is not
- 37:35containerized now, but we're going to
- 37:37containerize it soon. Then when I go to
- 37:40the docs, you can see the swagger doc,
- 37:44it is hit API. It should be high API
- 37:47actually.
- 37:48And then in the root, you can see when I
- 37:53execute it, it will reply with hello
- 37:55from inside the container and also the
- 37:58health end. whatever steps we have
- 38:00followed to execute and run this server.
- 38:03These are the same steps that we need to
- 38:05mention in our docker file to create our
- 38:07package. So let me close this and open
- 38:12up our project folder.
- 38:15I'm just going to copy this
- 38:18and create a new folder. Let me rename
- 38:21it to docker. And here I'm going to
- 38:24create a special file. Okay, this
- 38:27special file will be called let me
- 38:29delete this pi cache as well. We don't
- 38:32need it. All right, I have my new
- 38:34application folder here. I'm going to
- 38:37create a new file and this file will be
- 38:40called a docker file. I know they have
- 38:43kept the name very intuitive
- 38:45and this file will not have any kind of
- 38:47extensions. You simply call it docker
- 38:49file and you can see VS code or
- 38:51anti-gravity is able to identify it in
- 38:54this docker file. The very first thing
- 38:56that we need to mention is the base
- 38:58image from which we will start building
- 39:00our image. So the base image for this
- 39:03application will be a Python one. We can
- 39:06specify from
- 39:09and also the name of the base image.
- 39:12In our case, it is going to be Python
- 39:163.12
- 39:19slim. With this instruction, Docker will
- 39:21pull the image of Python 3.12 Slim and
- 39:25it will use it as a base image to
- 39:29package our application.
- 39:31Now the next thing that we can do is
- 39:34specify a working directory inside the
- 39:37container environment. As I told you
- 39:39already that every container will have
- 39:41its own file system. We need to specify
- 39:43the specific directory in which Docker
- 39:46will open up our application. So we
- 39:49specify it by work there. This simply
- 39:53stands for working directory. And I can
- 39:56specify the working directory as / app.
- 39:59What this will do internally is we all
- 40:02know docker runs in a Linux environment,
- 40:04right? So it will use the mkdir
- 40:08command and using this mkdir command it
- 40:11will create the app folder and then it
- 40:14will go to the app folder using cd. It
- 40:16will do cd app. Both of these things are
- 40:19done internally by Docker whenever you
- 40:21are specifying this work command. Okay,
- 40:24this thing is asked in the interview.
- 40:25That's why I'm just letting you know.
- 40:27So, Docker has created the working
- 40:29directory and it is now in that working
- 40:31directory. What are we going to do now?
- 40:33The very first thing that Docker
- 40:35requires is the application files. So,
- 40:39we need to copy all these application
- 40:40files like main.py or requirements.txt
- 40:43txt into the docker file system. For
- 40:47that, we can use the copy instruction
- 40:50and specify the current root folder that
- 40:54we want to copy. We want to copy this
- 40:56into wherever docker is right now. So,
- 41:00it is already in the app folder. We do
- 41:02not need to specify app. We can just
- 41:04specify dot. This will copy everything
- 41:07everything that is in the current
- 41:08working directory. You can see the
- 41:10current working directory is this one.
- 41:12Hello API Docker. It will copy all of
- 41:14these files into Docker's own file
- 41:17system. Now we have already CD into our
- 41:22working directory. Next thing that we
- 41:24need to do is the same thing that we did
- 41:26to run our app which is installing all
- 41:28the dependencies that are present in
- 41:30requirements. TXT. So I can use the run
- 41:35instruction which simply says that
- 41:38docker run this particular command that
- 41:41I'm specifying. So I can specify the
- 41:43command as pip install
- 41:47-ash r and I can specify it is
- 41:51requirements dot txt in this app folder.
- 41:56You see we have copied it already in the
- 41:58app folder. It will be present in the
- 42:00app folder. We are just going to use the
- 42:01same requirements file to download all
- 42:03the dependencies for our application
- 42:05inside docker's environment. And then we
- 42:08can finally specify the command that it
- 42:11needs to run whenever our container gets
- 42:14started. So remember everything that we
- 42:17have done so far will run only once
- 42:19while we are building the image. Once
- 42:22the image is built it is going to follow
- 42:24all these instructions if there is any
- 42:26change. Okay. If there is no change, it
- 42:28is only going to run this command.
- 42:31Whenever we use the start command with
- 42:33docker, whenever we say docker start the
- 42:36container name, it is going to use this
- 42:37command that we are going to specify.
- 42:39And this command is going to be the
- 42:42uvicon command. So I can specify uon
- 42:47everything that we said main app and I
- 42:50can also specify host here. I want the
- 42:53host to be local host, right? So I can
- 42:56write 0.0.0.0.
- 42:59I can also specify the port here. So my
- 43:01port is port the value is 8,000. So I
- 43:05will write 8,000. And with this my
- 43:07docker file is complete. It can now
- 43:09build an image and start a container for
- 43:12it. So let's do it in our command line.
- 43:14I'm going to clear up all this. We can
- 43:16use the docker build command to build
- 43:19our image. But make sure before you run
- 43:21this, you should have Docker desktop
- 43:24open. Okay, if you're using Windows
- 43:27specifically, you should ensure that
- 43:29your Docker engine is running.
- 43:30Otherwise, this won't work. You can see
- 43:32currently I do not have any images. So,
- 43:35we are going to see what this build
- 43:37command does, what changes happen in
- 43:39this docker desktop. So, I can run
- 43:44docker build and then I need to specify
- 43:47a tag. I need to give a name to the
- 43:50image that I'm building here. The tag
- 43:51that I'm going to give is let's say
- 43:54hello API and I need to specify which
- 43:59directory it needs to take as a
- 44:02reference for building this particular
- 44:03image. We are already in this zero we
- 44:07are in zero docker API. Let let us go to
- 44:11the one the one where our docker file
- 44:13lives. So I can go to cd one this and I
- 44:19can run the same command. So docker
- 44:22build-d
- 44:24which is our tag. Our tag is hello API.
- 44:28And then I can specify dot. This dot
- 44:31will say that use this folder the folder
- 44:34where I am in as a reference to package
- 44:37our app. So let me press enter. This
- 44:40should start building our image. You can
- 44:42see our image has started building and
- 44:44you can see that it is running
- 44:46everything command by command. So it is
- 44:48first pulling in this Python 3.12 slim
- 44:52from docker hub and then it is going to
- 44:54run each of these instructions that we
- 44:56have written to build our image. You
- 44:57will see that it will not run this cmd.
- 45:00Okay, you see it only executed until
- 45:02this run command. It is not executing
- 45:05the cmd. The cmd will run when we start
- 45:07up our container. Let's investigate if
- 45:10our image has been created or not. So I
- 45:12can do that by using docker image ls.
- 45:16You can see our hello API latest has
- 45:19already been created and it is using 148
- 45:23MBs. We have only two files main and
- 45:25requirements. Why do you think it is
- 45:27consuming 148 MBs? It is because we have
- 45:31already mentioned Docker will package
- 45:33everything that your application needs
- 45:36to run. So it has downloaded Python and
- 45:41the underlying OS which could be Alpine
- 45:43or some very minimal OS that is required
- 45:46to run your app. It would also be all
- 45:49the application files and apart from the
- 45:52application files all the dependencies
- 45:54that are being downloaded from pip. So
- 45:57fast API u any other dependencies that
- 46:00fast API and uicorn also need will also
- 46:04be bundled into this package. Okay, that
- 46:06is why you see 148 MB here.
- 46:09Now, let's start up our container and
- 46:13see it in action. Before that, let me
- 46:15also show you what change has occurred
- 46:18here. You can see in the image, I see
- 46:21hello API, right? So, my image is
- 46:24created and it is registered. Now, I can
- 46:26use the docker run command to start up
- 46:29our container. So, docker
- 46:33run and then I need to specify the name
- 46:36of the image. Let me call it hello API
- 46:40docker. I also need to specify the port
- 46:42and map it so that the port that is
- 46:45present inside the container. I said
- 46:48docker will create an isolated
- 46:49environment. So, it also creates an
- 46:50isolated network. So, I specify the port
- 46:53that is running inside the container and
- 46:55map it to a port in my computer. So this
- 46:58is the port in my computer. I'm mapping
- 47:00it with the port that is running inside
- 47:02the docker container. All right. And
- 47:04then I can specify the name of the
- 47:06image. In our case, it is hello API.
- 47:09When I press enter, you will see that it
- 47:12will run this cmd that we have
- 47:15mentioned. Okay. And this command is
- 47:16nothing but uicon main col app host and
- 47:20port. So when I follow this link, when I
- 47:24open it, it's not running. But when I go
- 47:28to local host, you will see that it is
- 47:30running. So 0.0.0 is not recognized.
- 47:34When I specify local host, it will run.
- 47:36All right. And when I stop this
- 47:39container, you can see when I will stop
- 47:42this container. Right now I'm not using
- 47:45a demon like dash d attribute.
- 47:49But when I stop this container, you can
- 47:50see server has stopped, right?
- 47:53Application shutdown complete. Let me go
- 47:56back to my chrome and you can see when I
- 48:00refresh this won't be reachable. See
- 48:04site can't be reached. So this was
- 48:06running through my docker container
- 48:09and let me run it again. Maybe let me
- 48:11start it again. I don't need to use the
- 48:13run command now. Let me clear this. I
- 48:16can run docker start
- 48:19hello API docker. And you see it has
- 48:22started the hello API docker. When I go
- 48:24to VS Code again, you can see that it is
- 48:27accessible. All right. When I open
- 48:30Docker desktop
- 48:32and click on containers, you see that
- 48:34this container is running. I can also
- 48:36see the logs. You saw we have hit the
- 48:40homepage and all of that is registered
- 48:42here. Congratulations. You have just
- 48:44containerized your first application. In
- 48:47the next few sections, we will dig
- 48:48deeper into Docker and understand more
- 48:50of its functionalities. Peter's first
- 48:52Docker image is built and his little API
- 48:55is running inside a container. He's
- 48:57already eyeing the chatboard, but Miles
- 49:00stops him. Not so fast. They still need
- 49:04faster builds, data that survives when a
- 49:07container gets replaced, and containers
- 49:10that can talk to each other. After all,
- 49:13the demo is tomorrow. And Bruce is glad
- 49:16someone remembers.
- 49:21>> In this section, I want to talk about
- 49:23layers in Docker. Let me show you
- 49:25something. I am opening up my terminal
- 49:28and I've just run this docker prune
- 49:31command to clear all my Docker
- 49:33artifacts. I'm going to build my image
- 49:36again using this docker build-d
- 49:39API do command.
- 49:42Notice when I'm building it, docker will
- 49:46run all of these commands that I've
- 49:48written line by line. So currently it is
- 49:51running this from docker and it is
- 49:54installing this python 3.12 slim image.
- 49:59After this is done, you see it ran this
- 50:02work then it it copied all the artifacts
- 50:04and then it is running pip install
- 50:06requirements.txt.
- 50:08This means that Docker is not packaging
- 50:11our application in one go into an image.
- 50:14What it is doing is it is building this
- 50:16image layer by layer and each
- 50:19instruction that we have provided in our
- 50:21docker file contributes to each new
- 50:23layer in this image. At the end, it also
- 50:25adds a new layer that is for anything
- 50:29that changes in the runtime. Okay. So
- 50:33here you can see we have four commands
- 50:35that are building the layers which is
- 50:37why like our docker image will have five
- 50:39layers in total four from each of these
- 50:42and the fifth one which is the runtime
- 50:45layer that docker adds and the reason
- 50:47docker does this layering is because of
- 50:49a feature called layer caching. Let me
- 50:52show you something. So you just saw that
- 50:55I built this image. Let me run the same
- 50:58command and build this image again.
- 51:01You will see it ran very fast. You see
- 51:05the cache output here. This means that
- 51:08Docker will first see if I have changed
- 51:10anything in any of these layers. For
- 51:13example, if I change the version, then
- 51:16Docker will redo the step and then it
- 51:19will do all the steps after the step
- 51:22that is changed. Similarly, if I change
- 51:25anything, if I change anything in my
- 51:29code, you can see this copy layer would
- 51:33have changed. And because this copy
- 51:36layer would have changed, Docker will
- 51:38cache these two commands. So, it will
- 51:41still run this cached command, but
- 51:46anything after copy, it will redo. Okay?
- 51:49So it will redo the copy and then it
- 51:51will redo this runpip install
- 51:53requirements.txt and it will redo adding
- 51:55this command. And this is why because we
- 51:57did not change anything. The second
- 51:59build that we did was a lot faster than
- 52:02our first build. You might have noticed
- 52:04that in the first build it was
- 52:07installing all the dependencies. It was
- 52:09downloading this Python 3.12 slim. In
- 52:13the second build it did not do any of
- 52:14those things. This is why it is very
- 52:17important for us to also notice how we
- 52:22have structured our docker file. Okay,
- 52:26the order of instructions in the docker
- 52:28file matter a lot because of layer
- 52:31caching. I will show you with an
- 52:33example. If I change anything in main,
- 52:36let's say I'm just going to add a very
- 52:40simple comment here like health
- 52:44endpoint. And because I have changed my
- 52:47main file, you see this copy layer is
- 52:50affected.
- 52:52And
- 52:53when I create a new image now you will
- 52:58see that this copy command will run
- 53:00again and all the commands after it will
- 53:03run again which means the pip install
- 53:05will also run again.
- 53:08You see copy ran and now pip install is
- 53:11running. This took a lot longer than
- 53:14whatever time we spent last time when we
- 53:17built the second image. Right? Do you
- 53:19think this is efficient? I don't think
- 53:21so. Why? Because we did not change
- 53:24anything in the requirements file. Still
- 53:27all our dependencies got installed again
- 53:30which is not a good thing because this
- 53:32will stall our build process. While
- 53:34building your docker file you need to
- 53:37ensure that the order of instruction is
- 53:39in such a way that whichever layer is
- 53:43going to change the most is in a later
- 53:46stage so that all the previous layers
- 53:48can be cached. All right. So, let's do
- 53:52something smart here. Let's implement
- 53:54layer caching for our Docker file here.
- 53:57So, I'm going to create a copy for this
- 54:00new section and we are going to update
- 54:03that Docker file. I can rename it to
- 54:07All right, I have copied all the code
- 54:10into this new folder and I'm going to
- 54:12modify the Docker file here. Okay, so
- 54:15that when you access this repository,
- 54:18you can see the whole history of its
- 54:20evolution.
- 54:22Let's see what we want to do here. Our
- 54:24main goal is to ensure that
- 54:27installations only happen when something
- 54:30is changed in requirements. .txt and
- 54:33they do not happen if anything else
- 54:36changes in the code because code changes
- 54:38will happen very regularly compared to
- 54:41change in any kind of requirement. For
- 54:43that we need to ensure that this runs
- 54:46before our copy command. So let me cut
- 54:50it from here and we will paste it after
- 54:54setting up our working directory. But
- 54:56there is a problem here. I will not have
- 54:58access to requirements.txt until
- 55:01everything is copied. So there is a
- 55:04workar around. We can copy only the
- 55:05requirements.txt file in our app folder.
- 55:08This way I will have requirements.txt
- 55:11txt file and my pip install will execute
- 55:14successfully. Try to understand what we
- 55:15are doing. We are first copying the
- 55:18requirements.txt file then installing
- 55:21all the dependencies and then copying
- 55:24all the code. So this layer comes very
- 55:26later on. I will try to do the same
- 55:27thing. So let us open up. We're going to
- 55:31cd onto our new directory. So cd
- 55:35to layer caching.
- 55:38And here I'm just going to run this
- 55:41command. Okay, I'm going to build my
- 55:43container again. Since this is a new
- 55:45one, everything will run. All right, you
- 55:49see the layers have changed. So it all
- 55:52ran. Now let me change something in the
- 55:54code.
- 55:57This is our code. Let me quickly just
- 56:02add another comment here. I will call
- 56:05this root
- 56:07endpoint
- 56:10and let's see how this change affects
- 56:13our docker build.
- 56:16I'm going to build again
- 56:21and you'll see everything was cached. So
- 56:25even the pip installation that earlier
- 56:28ran again because of a code change did
- 56:30not run this time and only the copy
- 56:33command ran here. So this is the benefit
- 56:37of layer caching. I would like to talk
- 56:39about something. These days AI can
- 56:42easily write these docker files. But
- 56:45what is important is having this
- 56:47intuition through understanding of all
- 56:51of these basic concepts that whether
- 56:53this file AI has generated is optimized
- 56:56or not. Okay. Which is why it is very
- 56:59important to learn all of these
- 57:02fundamentals and built on top of it.
- 57:07In this section, we are going to
- 57:09understand Docker volumes with a tiny
- 57:12experiment. Before that, I would like to
- 57:14show you some upgrades that we have made
- 57:16to our project and added some moving
- 57:18parts so that it is easier to understand
- 57:21Docker volumes. Let's look at the code.
- 57:23All I've done here is I have added a
- 57:26file and I've added a counter that will
- 57:29read this file and update the count of
- 57:33visits that we make to this particular
- 57:35API every time we hit this endpoint.
- 57:38What it is doing is it is making the
- 57:41directory if it does not exist. Then it
- 57:44is counting the number of visits that we
- 57:47have taken. So this will be one if there
- 57:49were no visits earlier. And then it is
- 57:52writing the number of count in the file
- 57:55and saving it. Every time we hit it, you
- 57:57see this read count is being executed.
- 57:59Which means that the file will be
- 58:01fetched. The current count will be read
- 58:04and then it will be incremented every
- 58:06time we open this particular API up. Let
- 58:10me run this and show it to you again. So
- 58:13I have opened up this API. I'm running
- 58:17it using Docker again.
- 58:20When I click on execute,
- 58:22you will see that the counter is one.
- 58:26When I execute again, counter is two.
- 58:29Let me execute it three more times. And
- 58:33the counter is five. When I go back to
- 58:35my code, you will see that this is being
- 58:38run in a Docker container. So, let me
- 58:42stop it. I'm going to stop it and I'm
- 58:47going to start this container again and
- 58:49the name is hello API docker. So hello
- 58:53API docker.
- 58:56When I run it again and I go back to
- 59:00this particular URL, let me refresh it.
- 59:03So if I do control R and refresh it and
- 59:07try out this count again, you will see
- 59:10that the count is six. which means that
- 59:12whatever changes I have made so far into
- 59:15my container, it is getting stored. Let
- 59:17us do another experiment. I'm going to
- 59:19stop it and then I'm going to remove
- 59:21this container. All I need to do is rm-f
- 59:24and remove it. Let me start this
- 59:26container again. And when I do this,
- 59:30I can refresh.
- 59:33And when I execute,
- 59:36you see the counter is beginning at one.
- 59:40Which means that all the data that we
- 59:42had stored earlier is now deleted
- 59:45just because we deleted our container.
- 59:48And this should not happen because I'll
- 59:49tell you in a typical developer
- 59:51workflow, containers are very
- 59:53disposable. Let's say you encountered
- 59:56any bug. You will fix that bug in the
- 59:58code. You will rebuild an image and then
- 1:00:00you will restart the container. or there
- 1:00:03is some runtime issue that occurred and
- 1:00:05the container stopped. In that case, the
- 1:00:08orchestration platform that you are
- 1:00:09using, it could be Kubernetes or
- 1:00:11something else, it will restart the
- 1:00:13container on its own automatically. Then
- 1:00:15again, your data will be gone. This is a
- 1:00:18problem and this is exactly what volumes
- 1:00:21solve. A volume will sit outside the
- 1:00:23Docker file system and it will help you
- 1:00:25persist all the data that is required.
- 1:00:27For example, any media files that the
- 1:00:29user uploads, any of the database
- 1:00:32records that you want to save, or any
- 1:00:35kind of log files, application logs that
- 1:00:37you want to store for observability. All
- 1:00:40of those things will live in this volume
- 1:00:42outside the Docker container. Okay? And
- 1:00:46to implement volumes, you don't really
- 1:00:48need to make any changes in the Docker
- 1:00:49file itself, but you need to make some
- 1:00:53change in the command that you are
- 1:00:55using. All right? So let me show you the
- 1:00:57command. I'm going to go back in my
- 1:01:00anti-gravity
- 1:01:02and here I'm going to paste the typical
- 1:01:05command that we run whenever we start
- 1:01:08our docker container.
- 1:01:10Okay, here I can use this dash v flag
- 1:01:16and specify my volume. So first I need
- 1:01:19to specify the folder in my directory in
- 1:01:22my local file system where I'm going to
- 1:01:25persist this volume data. I can call it
- 1:01:29counter data folder and I can map it
- 1:01:33with the folder where the docker file
- 1:01:35system is storing this data. That folder
- 1:01:39is / data. Once I do this, I will start
- 1:01:44my API. And you can see the counter
- 1:01:48shell start with wherever we left off.
- 1:01:52So let me refresh this. And after
- 1:01:55refreshing,
- 1:01:57you can see when I call this execute
- 1:01:59this counter,
- 1:02:01it is 9. Let me execute it again, it is
- 1:02:0510,
- 1:02:0711. Let me go back and delete this
- 1:02:10container. So
- 1:02:13I'm going to
- 1:02:16delete it. My container is deleted. I
- 1:02:19can docker ps to confirm there is no
- 1:02:22container running. Then
- 1:02:25I can run this command again with the
- 1:02:27volume and you will see that it will
- 1:02:30begin again with 11. Okay. So let me go
- 1:02:34back. I will do CtrlR to refresh
- 1:02:38everything.
- 1:02:40And when I execute, you see this should
- 1:02:42be 12. It is 12. See? So our data
- 1:02:45persist now using volume. 13 14. Yeah.
- 1:02:50So this is how we implement volume in
- 1:02:53Docker and they are used to persist data
- 1:02:56that you still want to persist even if
- 1:02:58the container restart or you delete the
- 1:03:00container and you build a new one. All
- 1:03:03right. In the next section, we will
- 1:03:05learn about something called multi-stage
- 1:03:07builds, which is another very
- 1:03:09interesting concept for optimizing any
- 1:03:11Docker build. Let's take a moment to
- 1:03:13thank today's sponsor, Open Router.
- 1:03:16Which model is your AI feature actually
- 1:03:19using in production? And is anyone even
- 1:03:21using it? Most teams ship an AI feature
- 1:03:24and fly blind. No visibility into usage,
- 1:03:28no idea if a newer model would be
- 1:03:30better. Open Router fixes this with app
- 1:03:33attribution. Add one header to your
- 1:03:36request and your app shows up in Open
- 1:03:38Router's public rankings daily, weekly,
- 1:03:42and monthly alongside a full analytics
- 1:03:45dashboard for your usage and token
- 1:03:47trends. You also get the full model
- 1:03:49catalog to compare against filterable by
- 1:03:53price, context, and benchmarks all on
- 1:03:56one API. 500 plus models automatic
- 1:03:59fallback pass through pricing. We use
- 1:04:02this to see which models our own tools
- 1:04:05actually lean on instead of assuming.
- 1:04:08Check out Open Router using the link in
- 1:04:10the description that is open router.ai.
- 1:04:12Now let's go back to our video.
- 1:04:17>> In the last two sections, we learned how
- 1:04:19to speed up our build time using layer
- 1:04:21caching and we also learned how to
- 1:04:24persist our data using the volume. In
- 1:04:26this section, we are going to talk about
- 1:04:28technique that can make our build times
- 1:04:30even faster. Before we actually
- 1:04:33understand what it is, let's look at the
- 1:04:35code and understand the problem that we
- 1:04:37have. So, I have my anti-gravity ID open
- 1:04:41here and I've updated the code a little.
- 1:04:44Let me walk you through it. Now, I have
- 1:04:46my main.py, but I also have test
- 1:04:49main.py. And this is what a typical dev
- 1:04:52workflow looks like. You will have some
- 1:04:55dependencies that are required for your
- 1:04:58actual application and there are some
- 1:05:00test dependencies that you may require
- 1:05:03during dev time or to ensure that
- 1:05:06everything is working correctly in your
- 1:05:09application. I also have some
- 1:05:10requirements dev.txt
- 1:05:13which contains pyest and httpx and I
- 1:05:16have requirements.txt that are the
- 1:05:18actual dependencies that I require in
- 1:05:20runtime of my application. And all of
- 1:05:23this change also reflects in my docker
- 1:05:25file. So when I open my docker file, you
- 1:05:28can see that we are using this 3.12
- 1:05:30slim. I'm creating the work there. Then
- 1:05:33I'm copying all the dependencies from
- 1:05:35requirement.txt
- 1:05:37and I'm also installing them. After that
- 1:05:40I'm copying my requirements.txt in my
- 1:05:43docker file system and I'm using pip to
- 1:05:46install all the dependencies in that
- 1:05:48file system. Then I'm copying the
- 1:05:51requirement dev.t pxt in my docker file
- 1:05:54system and I'm installing all of this
- 1:05:57into my docker container. After that I'm
- 1:06:00copying all the code files. Then I'm
- 1:06:03running this pi test main.py
- 1:06:06and this will run all these tests that
- 1:06:09I've written here. Right? It will test
- 1:06:12the root endpoint. It will test the help
- 1:06:14endpoint. And if both of these endpoint
- 1:06:16pass then only my build will be
- 1:06:19successful.
- 1:06:21All right. And then I've specified the
- 1:06:23command that it should run to start up
- 1:06:24my container. You can notice that we
- 1:06:27don't really need these dependencies
- 1:06:30during runtime. And we do not even need
- 1:06:32to do this during runtime because these
- 1:06:35are all the dependencies that we had to
- 1:06:37ensure that our development workflow is
- 1:06:40smooth. And this is the typical case in
- 1:06:42any production scenario. We have to
- 1:06:44optimize this docker file. We can do
- 1:06:46this by using something called
- 1:06:48multi-stage builds. The idea is pretty
- 1:06:50straightforward when it comes to
- 1:06:52multi-stage builds. All we are saying is
- 1:06:55we are going to split this whole docker
- 1:06:57file into two different stages. The
- 1:07:00first stage is going to be the build
- 1:07:02stage where all the heavy lifting will
- 1:07:03happen where we are going to download
- 1:07:06all the dev dependencies. We are going
- 1:07:08to run these tests and then we are going
- 1:07:11to have the runtime build. In the
- 1:07:14runtime build, we are only going to pick
- 1:07:16up whatever we need to run the
- 1:07:19application successfully. So let's
- 1:07:20understand this by using some hands-on.
- 1:07:23Here I have my docker file. I'm not
- 1:07:24going to change anything but I'm just
- 1:07:26going to calculate how much space the
- 1:07:28current build will take.
- 1:07:31Let me open up our terminal and I'm
- 1:07:35going to use the docker
- 1:07:38build command and I'm going to use the
- 1:07:40tag of API single because this is a
- 1:07:44single stage build right so single and
- 1:07:47then dot let's see
- 1:07:50it is building it and the build is
- 1:07:54complete let me see what is the size of
- 1:07:58the image so I will write docker
- 1:08:01And you can see it is currently 150 MB.
- 1:08:04So using our multi-stage build we are
- 1:08:06going to see how much we can reduce
- 1:08:07this. It is not going to be much because
- 1:08:10we have already kept our requirements
- 1:08:12very lean but still there should be some
- 1:08:15improvement.
- 1:08:16So what are we going to do? How are we
- 1:08:19going to implement the multi-stage build
- 1:08:20here? The very first thing that we need
- 1:08:22to do is we need to name our build
- 1:08:24stages. The first stage can be named as
- 1:08:28builder
- 1:08:30and we are going to use the as
- 1:08:32instruction to give it a name. So I'm
- 1:08:35going to call it builder. And then here
- 1:08:37I can add a prefix. So I need to ensure
- 1:08:41that whatever runtime dependencies I
- 1:08:44install in this build stage, I should be
- 1:08:46able to copy them into the runtime
- 1:08:49stage. Okay? Because I do not want to
- 1:08:51reinstall everything again. So that is
- 1:08:54what I'm going to do. I am simply
- 1:08:58going to add a prefix here that will
- 1:09:02ensure that all my dependencies are in a
- 1:09:05specific folder. I can name it install
- 1:09:08maybe. And with this I will ensure that
- 1:09:11all the dependencies that I need during
- 1:09:13runtime are installed in this specific
- 1:09:16folder inside Docker's file system.
- 1:09:20And then let's see what else we can do.
- 1:09:23This is going to remain the same. We
- 1:09:25need it to run our test. And we also
- 1:09:27need all the files. So you see that the
- 1:09:31test domain file is importing app from
- 1:09:35main. So we still need our app even in
- 1:09:38the build stage. Okay. And we have this
- 1:09:42piest test main.py-v.
- 1:09:45We going to keep it, but we are going to
- 1:09:47append the command. Let's see what the
- 1:09:49command would look like. All I'm going
- 1:09:51to do here is
- 1:09:54I am going to
- 1:09:57use the Python version that we have
- 1:10:00installed in this install path. All
- 1:10:02right. Through this what I'm ensuring is
- 1:10:06that all the runtime dependencies are
- 1:10:09also available to my Python. Okay.
- 1:10:13with this run command. Our builder stage
- 1:10:17is complete. So I can create a new stage
- 1:10:21now and this stage can be named as a
- 1:10:25runtime stage. Now remember every time
- 1:10:28we are starting out a new stage we need
- 1:10:31to start with another base image for us
- 1:10:34the base image is going to be this and I
- 1:10:37can name it runtime. All right. And here
- 1:10:40I'm going to copy a few things from this
- 1:10:43builder stage. Before that I will also
- 1:10:46specify that the directory name here
- 1:10:49should be app. So I'm going to use copy
- 1:10:52and then I can use this from tag. Okay.
- 1:10:56So I can use this from tag. And from
- 1:11:01the name from which I'm going to copy is
- 1:11:04called builder. And from builder what am
- 1:11:06I going to copy? I am going to copy this
- 1:11:10install folder. You see because we have
- 1:11:12mentioned prefix install all these
- 1:11:15libraries that are required for runtime
- 1:11:18are going to be stored here. So I'm just
- 1:11:20going to take all of these libraries
- 1:11:24from the install folder and I'm going to
- 1:11:29add them in my user local folder. Okay.
- 1:11:35So what this will do is it will ensure
- 1:11:38that all my dependencies are stored here
- 1:11:41and then I can copy all my runtime code
- 1:11:45from my file system. You can see I only
- 1:11:49need the main.py here. So I'm only going
- 1:11:52to copy main.py.
- 1:11:55So I'll copy this main.py
- 1:11:59into my app folder which is dot. Okay.
- 1:12:04Once I do this, I can run the same
- 1:12:07command and my build is ready. So you
- 1:12:10can see there are two stages. How do we
- 1:12:12know there are two stages? Because we
- 1:12:14have used two base images. All right,
- 1:12:17you can see this is the first one. This
- 1:12:18is the second one. Let me add a comment
- 1:12:22here. So I will say that this is stage
- 1:12:24runtime. And let me add another comment.
- 1:12:27It is always a good practice to document
- 1:12:29your stages, right? So I'm just simply
- 1:12:34going to add a comment saying that this
- 1:12:37is stage builder. Okay, we can now run
- 1:12:41our build command. So you see the image
- 1:12:45is 150 earlier.
- 1:12:48Let me clear this up.
- 1:12:51I'm going to run this build command but
- 1:12:53now I'm going to call it multi-stage.
- 1:12:55Okay, let's see. So my build has
- 1:12:57started.
- 1:12:59Dependencies are getting installed. See
- 1:13:01dependency got installed
- 1:13:04and you see there are eight layers in
- 1:13:06the builder image only two layers in the
- 1:13:10runtime image. Okay, you can see all the
- 1:13:12layers here. Now let us compare the
- 1:13:14sizes. Let's see if the multi-stage
- 1:13:17build has actually affected the size of
- 1:13:19our image or not. So I can run docker
- 1:13:23images and you can see that the
- 1:13:26multi-stage build
- 1:13:28is using
- 1:13:30somewhat of a less amount of disk.
- 1:13:34So you can see I'm building smaller
- 1:13:36images here. Now our dependencies were
- 1:13:40very lean. Okay, but this is really
- 1:13:43useful in production scenarios where you
- 1:13:45have bulked up dev dependencies that you
- 1:13:47do not want in your runtime and using
- 1:13:50multi-stage builds, you can keep your
- 1:13:52image size leaner and your build time
- 1:13:56faster. See you in the next section
- 1:13:58where I'm going to talk about networks
- 1:14:00in Docker. To understand networks in
- 1:14:02Docker, let's do a very tiny experiment.
- 1:14:06I have my anti-gravity open here and
- 1:14:09I've opened up the terminal.
- 1:14:11So let's build the same image that we
- 1:14:15built earlier but with a twist. So you
- 1:14:17see this is the command that we have
- 1:14:19been using so far where we are creating
- 1:14:22our container
- 1:14:24and we are using this port mapping thing
- 1:14:26right we use this -p right now we just
- 1:14:29thought okay in our docker file we have
- 1:14:32mentioned
- 1:14:34port 8000 that is why we are just
- 1:14:37mapping these things together right so
- 1:14:40that is what we did earlier let me
- 1:14:43remove it and then execute this run
- 1:14:49command. So let me explain you the whole
- 1:14:51anatomy of this run command.
- 1:14:54We have docker run. Then I've specified
- 1:14:57name of the container. Okay. After that
- 1:15:00I have done this for volume mapping. And
- 1:15:03then I have used this image name image
- 1:15:06tag as API multistage. This command
- 1:15:08simply says that use the API multi-stage
- 1:15:11image to start up a container and give
- 1:15:14it the name of API with volume mapped to
- 1:15:19this particular folder. Okay, that is
- 1:15:21what this is doing. I have removed the
- 1:15:24-p port mapping flag here. Let's run
- 1:15:28this and see what happens. Okay, it
- 1:15:30seems like there is already a container
- 1:15:33named API. So, I'm just going to remove
- 1:15:35it. Let me remove this API container
- 1:15:39and run it again. You see I do not have
- 1:15:42this dash p flag here. When I execute
- 1:15:46this, you will see that this get
- 1:15:48started. But if I follow this link,
- 1:15:54this is not opening. But this was not
- 1:15:56opening earlier as well because I think
- 1:15:58Windows does not understand 0.0.0.
- 1:16:02Let's go to local host and see. Wait a
- 1:16:04minute. This is also not working. Why is
- 1:16:07it not working? Let's try again. Again,
- 1:16:12we have the same issue.
- 1:16:14When I go back to my terminal, I stop
- 1:16:18this container.
- 1:16:20And then
- 1:16:22when I rerun it by adding this
- 1:16:28-p, this time I'm going to do something
- 1:16:30different. This time I'm going to name
- 1:16:32this as 9,000
- 1:16:35colon
- 1:16:378,000. Okay,
- 1:16:40let's see. Now
- 1:16:43again there is already an API container.
- 1:16:45So let me remove it and
- 1:16:49create this container again. Remember
- 1:16:539,000 col 8,000 is what we have written
- 1:16:57for port mapping.
- 1:16:59Let's go back
- 1:17:01to our browser and try it again.
- 1:17:07Again, failure. Let me change this to
- 1:17:119,000.
- 1:17:15We got a response.
- 1:17:17When I go to docs, you will see I still
- 1:17:20get a response. I can open the count
- 1:17:23endpoint, try it out, and execute. And
- 1:17:28you can see count is one. I will execute
- 1:17:31again. Count is two. Again count is
- 1:17:34three. This is working. But why did it
- 1:17:38not work for port 8,000? Let me go back
- 1:17:41to 8,000.
- 1:17:43See, I'm getting an error. The point is
- 1:17:47what I want to tell you or explain you
- 1:17:49with this experiment is we need to map
- 1:17:53the port of our local host with the port
- 1:17:58which is running inside the container
- 1:18:00because internally Docker runs in an
- 1:18:04isolated process which means that Docker
- 1:18:07has its own network, its own local host,
- 1:18:10its own ports and until we use this port
- 1:18:14mapping to expose
- 1:18:15its internal ports we cannot access it.
- 1:18:20All right. So even though Docker is
- 1:18:22running it in its internal port 8,000 we
- 1:18:26will not be able to access it until we
- 1:18:28use this port mapping. That is why we
- 1:18:30were using this flag ever since the
- 1:18:32beginning.
- 1:18:36I wanted to make our project a bit more
- 1:18:38realistic for this particular section
- 1:18:41which is why I have changed the
- 1:18:43architecture a bit. Now I have
- 1:18:46introduced radius in our system. Radius
- 1:18:49will act like our database so that
- 1:18:52whenever the counter is increased that
- 1:18:54data is being stored in this radius
- 1:18:57server. So we have to spin up radius as
- 1:18:59a separate container. Fast API will be a
- 1:19:01container in itself and both of them
- 1:19:03will communicate every time somebody
- 1:19:05executes the counter endpoint. All
- 1:19:07right, let me show you how this is
- 1:19:09reflected in the code itself. I'm back
- 1:19:11in my anti-gravity ID and I've opened up
- 1:19:14this project folder. Let's start with
- 1:19:17the change that we have made here. I'm
- 1:19:20not using requirements.txt anymore. I'm
- 1:19:22not using pip as our package manager.
- 1:19:24Now we have shifted to something called
- 1:19:26UV which is a lot faster than pip and
- 1:19:30this is what most of the people most of
- 1:19:33the developers they use in industry
- 1:19:36level project because uv is featurerich
- 1:19:39and it is a lot faster than pip. It also
- 1:19:42creates a log file that will provide the
- 1:19:45specific wheels and the specific
- 1:19:48binaries for all the dependencies that
- 1:19:50you have installed. So you can see we
- 1:19:52only have fast API, radius and uicon but
- 1:19:56the log file will contain all the other
- 1:19:58dependencies as well that these three
- 1:20:00are dependent on. Then I have made some
- 1:20:03minor changes in the code where I'm
- 1:20:05first connecting with the radius client.
- 1:20:07So radius will have its own server. It
- 1:20:10will also run in my local host. I'm
- 1:20:13using this radius client from radius to
- 1:20:16connect with that server and then I'm
- 1:20:19just incrementing the counter here.
- 1:20:21Okay, this is a very basic change.
- 1:20:23Radius is not the interest of this
- 1:20:25course. I'm just introducing it as a new
- 1:20:28service and that's all you need to know
- 1:20:30about it. Let's look at the changes that
- 1:20:33occur in our docker file.
- 1:20:36You can see that we are still using this
- 1:20:393.12 slim but now we do not have pip as
- 1:20:43our package manager. We need to install
- 1:20:45UV. There are two ways to do it. One I
- 1:20:48go to the UV page. Okay. And you can see
- 1:20:55it has this installation guide. One way
- 1:20:58is I can come here. I can copy this
- 1:21:01command and I can execute it using the
- 1:21:05run instruction in my docker file. But
- 1:21:07this will be a longer task because I
- 1:21:10will first download the installer. The
- 1:21:12installer will then get executed. It
- 1:21:15will create the binaries and it will be
- 1:21:17a longer thing. But what UV has done for
- 1:21:19us is they provide us with the
- 1:21:23containers directly.
- 1:21:25So if I go to this particular URL, I can
- 1:21:29just search for it on GitHub as well and
- 1:21:31you will see that in the UV repository
- 1:21:33in the packages
- 1:21:35they provide the image of UV. So I can
- 1:21:39directly access this binary
- 1:21:41you see docker pull gcr is like github
- 1:21:44container repository
- 1:21:46in there they have provided the uv
- 1:21:48binaries. So I can directly copy this
- 1:21:50and build my image from it. So that's
- 1:21:52what we have done here. We have accessed
- 1:21:57this UV image, the binary from this
- 1:21:59image
- 1:22:01and we have copied the UV folder from
- 1:22:06here to our user local bin in the file
- 1:22:10system of our docker image. Okay. Then I
- 1:22:14have created the work there app. I'm
- 1:22:17setting up this UV project environment.
- 1:22:19This is similar to the prefix flag that
- 1:22:23we added in pip. So all the dependencies
- 1:22:26will be stored in this optv
- 1:22:30because I want to keep the dependencies
- 1:22:32out of this / app. Once this is done, I
- 1:22:36copy the pyro.l file which contains the
- 1:22:40list of our dependencies
- 1:22:42and I copy it in the file system of my
- 1:22:46docker along with uv.lo. UV.lock clock
- 1:22:48is also present here. So I'm copying
- 1:22:50both this file and this file in my
- 1:22:53docker file system. Then just like we do
- 1:22:57pip install in uv we do uv sync and then
- 1:23:01I'm saying that do not install any dev
- 1:23:04dependencies and no install project.
- 1:23:07Okay, I'm adding the appropriate flags
- 1:23:09here. Then I'm copying all the files all
- 1:23:13these files into my docker file system.
- 1:23:16I'm setting the environment path as this
- 1:23:19because this is where all my binaries
- 1:23:21are installed. All my binaries like UV,
- 1:23:24all the packages that I'm going to
- 1:23:26install, all of those are installed
- 1:23:28right here. After that, I annotate this
- 1:23:33expose. This is like a documentation of
- 1:23:36the ports that you are trying to expose.
- 1:23:38Okay? So, I'm just mentioning that I I
- 1:23:41will expose port 8,000 through this app.
- 1:23:44Once this is done, we can build this
- 1:23:46container.
- 1:23:48And to run it, we will also require
- 1:23:53the radius container. So let me show you
- 1:23:56how to run this app step by step. And it
- 1:23:59is going to be a long journey. I want to
- 1:24:01show you the pain before actually
- 1:24:04showing you how we can ease out that
- 1:24:06pain with Docker Compose. So let me copy
- 1:24:10the commands one by one and run them.
- 1:24:13The very first thing that we need to do
- 1:24:14is we need to create a network between
- 1:24:19both our containers so that they are
- 1:24:21able to communicate with each other. I'm
- 1:24:23going to use the network command to
- 1:24:25create a network with the name demonet.
- 1:24:28When I press enter, you see that the
- 1:24:30network is created. Now I can also
- 1:24:33create a volume for my radius. All
- 1:24:36right. So I can use this docker volume
- 1:24:40create radius data. This will create our
- 1:24:43volume and this volume will be present
- 1:24:47in our docker desktop as well. So you
- 1:24:50can see in volumes you will find this
- 1:24:53radius data. You see this is radius
- 1:24:55data. Counter data is also there. This
- 1:24:58is something we created one day ago. You
- 1:24:59can ignore all the rest. This is not
- 1:25:02needed but just focus on this radius
- 1:25:05data. Okay. And then I need to run a
- 1:25:10radius server. To run this radius
- 1:25:12server, I can pull it from docker hub
- 1:25:17and then run it. Or I can directly use
- 1:25:20this run command with the exact image
- 1:25:23that I require. So I'm going to use this
- 1:25:25radius 7 alpine image. And these are all
- 1:25:29the same commands that you have learned
- 1:25:31so far. I'm just specifying the network
- 1:25:33that this radius container it needs to
- 1:25:36run on the demonet that we just created.
- 1:25:39It needs to use this radius data as the
- 1:25:41volume
- 1:25:43and I'm going to name it radius server.
- 1:25:46Okay. And up and only yes means this
- 1:25:48this will only get updated. All right.
- 1:25:51So let me press enter. And you can see
- 1:25:53it is pulling the image of radius
- 1:25:57and it is going to start my container.
- 1:25:59All right. Let's docker
- 1:26:03ps. And you can see my radius container
- 1:26:06is running. Once my radius container is
- 1:26:08running, then only I can start up this
- 1:26:12container. Why? Because you can see my
- 1:26:16application depends on radius. All
- 1:26:18right. So radius is running. I can clear
- 1:26:23this out and I can say docker build-d.
- 1:26:28I'm going to name it API v3. Okay, I'm
- 1:26:32going to name it API v3 using colon. You
- 1:26:35can specify the version and this needs
- 1:26:38to happen
- 1:26:41in wait let me go to the docker compose
- 1:26:44and here I need to run that docker
- 1:26:48build- t api col v3
- 1:26:52and then I can specify the folder. So
- 1:26:56this is the folder five docker compose.
- 1:26:59When I press enter, you can see that my
- 1:27:03container is built now. And once this is
- 1:27:05built, I can start it. All I will do is
- 1:27:10I'm going to run this command
- 1:27:13of docker run. So let me clear this up.
- 1:27:16And because my image has been built, I
- 1:27:19can start up my container now. And you
- 1:27:22can see I'm going to use this particular
- 1:27:24command here where I've specified docker
- 1:27:28run-d.
- 1:27:29I'm giving it the name API and the
- 1:27:32network name is demonet. Then I have
- 1:27:35done the port mapping to port 8,000. And
- 1:27:38then the radius host is supposed to be
- 1:27:41radius. I'm supposed to provide the name
- 1:27:44of the container that is running in
- 1:27:46order to make that network successful.
- 1:27:49And then I specified API v3 here. Okay,
- 1:27:53this is the image that we are going to
- 1:27:56use to run our container. When I press
- 1:27:59enter, you can see my container is
- 1:28:02started. So when I see docker ps, you
- 1:28:05will see that the container is running.
- 1:28:08Let us go to local host and test it. All
- 1:28:11right. So give me a second while I pull
- 1:28:14up the browser. And I need to change the
- 1:28:18port to 8,000. Right? You can see with
- 1:28:21port 8,000 I still have my root and
- 1:28:25count and health endpoint. So I can
- 1:28:28click on this try out and when I execute
- 1:28:32you will see we have a new counter and
- 1:28:34now this is stored in radius. All right.
- 1:28:37So let's execute it few more times and
- 1:28:39you can see it is all being stored into
- 1:28:43radius. But do you see how painful it
- 1:28:46was with just two containers? Now
- 1:28:48imagine that instead of having these two
- 1:28:51containers, let's say I have something
- 1:28:56that does not look like these two
- 1:28:58containers, but something that looks
- 1:29:01like this. This is the architecture
- 1:29:03diagram of an e-commerce platform. And
- 1:29:07this is what they typically look like.
- 1:29:09You may have multiple different services
- 1:29:11written in multiple different languages.
- 1:29:14They can use different kind of services
- 1:29:17and containers for them like they can
- 1:29:19use Kafka container, rabbit MQ, we just
- 1:29:22saw radis. There can also be database
- 1:29:24containers like Postgress, MongoDB
- 1:29:27and some for observability. Do you think
- 1:29:31it will be easy to run and start up
- 1:29:35everything one by one and follow all the
- 1:29:38trail of commands that we did to just
- 1:29:42start up the application? No, it is
- 1:29:44going to be very painful. That is why
- 1:29:46the solution to this is provided by
- 1:29:48something called docker compose. We are
- 1:29:52going to see docker compose in detail by
- 1:29:55writing a compose file for the
- 1:29:57application that we just built. All
- 1:30:00right. So let's get started with the
- 1:30:02hands-on for docker compose. The very
- 1:30:05first thing we need to do is we need to
- 1:30:07create a compose file. And before this
- 1:30:10by the way since I'm using windows
- 1:30:13docker compose is packed with this
- 1:30:15docker desktop. Okay I have docker
- 1:30:17compose builtin since I downloaded this
- 1:30:20docker desktop and installed it locally
- 1:30:22in my computer. If you have done so on
- 1:30:25Windows or in Mac, you will already have
- 1:30:28Docker Compose. But in case you're using
- 1:30:31a Linux machine, you need to install
- 1:30:33Docker Compose. And you can just search
- 1:30:37for Docker Compose install.
- 1:30:41And you will say how to install Docker
- 1:30:44Compose for Linux. You can just follow
- 1:30:47the steps here, right? You can follow
- 1:30:49the steps for Ubuntu. Uh you will see a
- 1:30:52certain commands. We can follow this,
- 1:30:54execute them and your docker compose
- 1:30:56will be installed. A good way to check
- 1:31:00if you already have docker compose or
- 1:31:02not is to just run docker
- 1:31:08compose
- 1:31:10version. And you can see I already have
- 1:31:14docker compose installed in my computer.
- 1:31:17You can check it. If it is not there,
- 1:31:19please follow the steps to install it.
- 1:31:21it is going to be very easy. If you're
- 1:31:23using something like anti-gravity or
- 1:31:24cloud code, it's even easier. You can
- 1:31:27ask your coding assistants to help you
- 1:31:29with it. Right? So once you have your
- 1:31:32docker compose installed,
- 1:31:35you can create a new file and this new
- 1:31:38file can be named as compose dot yaml.
- 1:31:44All right, I press enter. My
- 1:31:47compose.yamel
- 1:31:49is ready. You can see that docker is
- 1:31:50picking it up. The very first thing that
- 1:31:52we need to do is we need to give a name
- 1:31:55to this compost file. So I'm going to
- 1:31:57call it hello API. And then I can list
- 1:31:59down all the services that are required
- 1:32:02to run this particular app. I can use
- 1:32:04this services command. And the first
- 1:32:08service that I'm going to run is radius.
- 1:32:11All right. Now let me open up the
- 1:32:14terminal and show you how to write a
- 1:32:17compos file. You can see that this is
- 1:32:19the command that we executed to start up
- 1:32:21our radis container. Right? So that is
- 1:32:24the same thing that I need to do here.
- 1:32:26This by the way is just the name. Okay,
- 1:32:29I can also call it database or something
- 1:32:31else. But for the sake of naming, I'm
- 1:32:35going to call it radius itself. Then you
- 1:32:38need to specify the specific image that
- 1:32:41you want to pull when this particular
- 1:32:44container runs. So the image that I want
- 1:32:46to pull you can see name is radius here
- 1:32:49that's why I have given radius. Next is
- 1:32:52the image I wanted to pull. So I need to
- 1:32:55write image and the image name here is
- 1:32:59this. It is radius alpine. I copy it and
- 1:33:04I paste it here. All right. You can see
- 1:33:08radius alpine is the image name. And
- 1:33:11then the next thing that I need to do is
- 1:33:15I need to run any kind of command. If
- 1:33:17there is any kind of command that I need
- 1:33:19to pass to this image to start up the
- 1:33:22container, then I can specify that
- 1:33:25command as well. So you can see the
- 1:33:26command here is radius server append
- 1:33:30only. Yes. Okay. This is the command
- 1:33:33that we have specified. So I can specify
- 1:33:37the command here. This is equivalent to
- 1:33:40you know the run that we had. So this
- 1:33:42will also execute this command in my
- 1:33:45container. So I just copied it and I'll
- 1:33:48paste it here. Okay. After this you can
- 1:33:51see that we have also specified volume.
- 1:33:54We have done some volume mapping and
- 1:33:57network as well. We can do the same
- 1:33:58here. We can specify the volume like
- 1:34:01this. You can use the volumes. And in
- 1:34:04the volumes you see that we have this
- 1:34:06radius data colis. I'm going to copy it
- 1:34:09and I'm going to paste it here. Ignore
- 1:34:12uh this Ctrl C because of copy this came
- 1:34:16up but I hope this is making sense.
- 1:34:18Basically you take all the command that
- 1:34:21you have used to start up any container
- 1:34:24any dependent services and then and you
- 1:34:27just convert them into this YAML
- 1:34:29structure that docker compose expect. So
- 1:34:31there are some configurations that you
- 1:34:33provide whenever you use the docker run
- 1:34:36command to start up a container. Those
- 1:34:38are the same configurations that you
- 1:34:41need to provide in this YAML compose
- 1:34:44YAML file. We can build the network
- 1:34:45later on. The next thing that we need to
- 1:34:48do is we need to specify our fast API
- 1:34:52container. So we called it API and the
- 1:34:55image that we used for this API
- 1:34:58container
- 1:34:59is we use the build command
- 1:35:03in the same directory where this compost
- 1:35:05file is living. So you can see it is
- 1:35:08living in this docker compose that's
- 1:35:09where we went and we ran our docker
- 1:35:11build do command. Right? So that's what
- 1:35:14we are specifying here as well. After
- 1:35:17that is there anything else that we did?
- 1:35:20So let's open up our API command. And
- 1:35:24you can see that we have specified some
- 1:35:26environment variables. - E stands for
- 1:35:28environment variables. We have also
- 1:35:30specified this port mapping. We have
- 1:35:33also specified the name which is API.
- 1:35:36Right? So these things we need to create
- 1:35:38here as well. I can specify the port
- 1:35:40mapping here. And what I'm going to do
- 1:35:43is I'm just going to copy paste the port
- 1:35:46that we get. So we are going to use the
- 1:35:49port as an environment variable here. So
- 1:35:51the default value for this environment
- 1:35:53is 8,000. All right. So we are mapping
- 1:35:55it with 8,000. And then we can also
- 1:35:58specify some environment variables that
- 1:36:00we have used. So in this - e we have
- 1:36:02used this radius host. So I can specify
- 1:36:05environment. And you can see name and
- 1:36:07value. name here is radius
- 1:36:11host and value here is radius which is
- 1:36:16going to be the name of the container
- 1:36:18that is that our container is dependent
- 1:36:20upon since we are running on a bridge
- 1:36:23network. Okay, docker has multiple types
- 1:36:25of network. We are using a bridge
- 1:36:26network here and we also need to specify
- 1:36:30if there are any dependencies for this
- 1:36:32particular container. Yes, it is
- 1:36:35dependent on this radius container. I
- 1:36:38can specify depends on the service name
- 1:36:42that it is dependent on is radius. All
- 1:36:45right. So after this I can also specify
- 1:36:50uh restart and stop or something else if
- 1:36:52it needs to restart or anything. But no
- 1:36:55the final thing that we need to do is
- 1:36:57specify the volume. Volumes will be
- 1:37:00radis data. All right, with this our
- 1:37:03compost file is ready and we can do this
- 1:37:05for as many services as we have. In the
- 1:37:08diagram that I showed you, there will be
- 1:37:10a compost file with all of these
- 1:37:13services listed
- 1:37:15like this. Okay, and the respective
- 1:37:18configurations that they want to run
- 1:37:21successfully. With this, our Docker
- 1:37:24compos file is ready. Let me show you
- 1:37:26how to execute it. Now to execute it,
- 1:37:30I'm first going to remove all the
- 1:37:32containers that are lying right now. So
- 1:37:35you can see if I do docker ps, you see
- 1:37:37we still have radius and api running. So
- 1:37:42let me remove them first. To remove
- 1:37:44them, I can docker
- 1:37:46rm-f
- 1:37:48and I can specify the name which is api
- 1:37:51and radius. Right? So api and radius are
- 1:37:54deleted. Let me see. I do not have
- 1:37:56anything lingering here. So I can clear
- 1:37:59it. And let me also delete the volume. I
- 1:38:01want to delete everything. All the
- 1:38:02networks, everything. I want to start
- 1:38:04from scratch using this docker compose.
- 1:38:07So this is all done. My docker is clean.
- 1:38:11Now what I can do now is execute this
- 1:38:14compost file. And to execute this
- 1:38:16compost file, you simply docker compose
- 1:38:20up. And then we need to specify whether
- 1:38:22we need to build images first before
- 1:38:24running or not. So we need to build our
- 1:38:26image. That's why I will say docker
- 1:38:29compos app dash built. Once I execute
- 1:38:32this you will see okay I'm getting this
- 1:38:34error. This is because it expects a
- 1:38:36correct yl and you see I do not have any
- 1:38:39space between this which is why it
- 1:38:42failed. It needs to be an array when I
- 1:38:46run it now. So this needed a space.
- 1:38:48Okay. when I run it again.
- 1:38:51Oh, even ports.
- 1:38:54All right, folks. Let me ensure that the
- 1:38:56YAML file is correct. Yeah, it is
- 1:38:58correct. It also needed a space. This
- 1:39:00should work. Now, I had also missed a
- 1:39:03semicolon here. So, after adding the
- 1:39:04semicolon,
- 1:39:06you can see that my image has started
- 1:39:10building. After running the docker build
- 1:39:11command, it is pulling the radius image
- 1:39:13because we just cleaned up everything.
- 1:39:15Radius image is pulled. Now it is
- 1:39:18building our fast API server and it is
- 1:39:21pulling the Python images. You can see
- 1:39:23it is now pulling the UV from this
- 1:39:26GitHub container repository. UV binaries
- 1:39:28are pulled and our application is
- 1:39:32started. You can see our application is
- 1:39:34started. I can now follow this link. So
- 1:39:38let me go back
- 1:39:42and you see my server is started now.
- 1:39:45It will start from scratch because
- 1:39:48I remember deleting the volume as well.
- 1:39:51So when I execute you see count is one
- 1:39:54and it is running on radius. And this
- 1:39:57time we have not started all the
- 1:39:59containers individually. We just
- 1:40:01mentioned everything
- 1:40:04in our docker compose file and we
- 1:40:07started them all together at once. All
- 1:40:10right. This is the beauty of docker
- 1:40:11compose. With this section, we are ready
- 1:40:14to now dockerize our rack chatbot. And
- 1:40:18in that rack chatbot as well, when we
- 1:40:21dockerize it, we are also going to build
- 1:40:23another compost file. So stay tuned. See
- 1:40:26you in the next section.
- 1:40:30Finally, the time has come to dockerize
- 1:40:32this telecom chatbot and help Peter
- 1:40:35deploy his application. This is the
- 1:40:37chatbot that we're going to dockerize.
- 1:40:40And I'm not going to go into the
- 1:40:41specifics of how this was implemented.
- 1:40:43If you're really interested to learn
- 1:40:45that, we will link the video somewhere
- 1:40:48in the description or above. You can
- 1:40:50check it out. And if you want the
- 1:40:53specific code, you will find the
- 1:40:54repository in the description. So let's
- 1:40:57look at an overview of how this is
- 1:41:00implemented.
- 1:41:01You can see we are using Chromma DB
- 1:41:03here, which is a vector database.
- 1:41:07And this could be a separate service on
- 1:41:09its own. And usually in production
- 1:41:12scenarios that is how you do you have a
- 1:41:14separate service for your DB. So this is
- 1:41:18the first service that we will be
- 1:41:20dockerizing. And then if you see then
- 1:41:22this particular rack chatbot is using an
- 1:41:25embedding model that is running locally
- 1:41:28via hugging face. Again embedding models
- 1:41:32like this are very resource intensive.
- 1:41:35So it is always recommended that you run
- 1:41:37them in a separate service. Okay. So
- 1:41:39that it is not interfering with the main
- 1:41:42chatbot.
- 1:41:44So we can create a separate service for
- 1:41:47this embedding model as well. And that
- 1:41:49is what we are going to do. We are using
- 1:41:53Quen. I think Quen is outdated now. So
- 1:41:56we'll be using GP OSS. That is one
- 1:41:58change I will do. But this is being
- 1:42:00served by Gro API. So we do not need to
- 1:42:02interfere with this. This will just be
- 1:42:04like any other API call. So we have
- 1:42:06three services now. One is the embedding
- 1:42:09model that we are going to deploy. Then
- 1:42:12we have our rack chatbot and we have our
- 1:42:15chrom. Luckily for chroma, chroma
- 1:42:17provides us with a docker image. So we
- 1:42:20don't have to build anything from
- 1:42:22scratch. We can use that image in our
- 1:42:24compos file and get started with it. We
- 1:42:26just need to connect it with our
- 1:42:28services. But we need to create a
- 1:42:31separate service for embedding model and
- 1:42:34we need to extract it out of our rag and
- 1:42:36provide this embedding service in form
- 1:42:38of a rest API. That's exactly what we
- 1:42:41have done here in the code. I'm not
- 1:42:43going to show you how to implement it
- 1:42:45from scratch because that won't be
- 1:42:47required. So I'm back in my anti-gravity
- 1:42:50and here I have created a new directory
- 1:42:53for telecom rack. When I expand it, you
- 1:42:56can see that I have already extracted
- 1:42:58the embedding service.
- 1:43:00Let me go through the code once. It is
- 1:43:02very simple. All I have done is we have
- 1:43:08created a fast API server. I have
- 1:43:11implemented a health endpoint just to
- 1:43:13let any service know that okay this is
- 1:43:16healthy and it is reachable. And then in
- 1:43:20this embedding path
- 1:43:23we have simply used the model to encode
- 1:43:25whatever text we are getting in the
- 1:43:27request and then we are converting into
- 1:43:29a numpy array. We are sending it out as
- 1:43:32a list. This is all we have implemented
- 1:43:35here. But you can see we are using
- 1:43:38sentence transformer to load our model
- 1:43:40into the device that is being provided
- 1:43:42by the environment. So device can be
- 1:43:45either CPU or CUDA. If it is CPU it will
- 1:43:47use the CPU. If it is CUDA then it will
- 1:43:51use the GPU. Ideally in a production
- 1:43:53scenario you will implement Docker files
- 1:43:55for both one which contains all the CPU
- 1:43:58dependencies and one that contains all
- 1:44:01the GPU dependencies. So that whenever
- 1:44:04you are deploying it is just one config
- 1:44:06change that you need to make. So we are
- 1:44:09going to do that and we are going to
- 1:44:10implement the docker file for our
- 1:44:13embedding service. First there is one
- 1:44:14thing I would like to discuss here which
- 1:44:16is docker ignore. You might have seen it
- 1:44:19in the previous few sections as well.
- 1:44:23You see, we do not want docker to pick
- 1:44:27up the venv or the pi cache or the bite
- 1:44:31code files that get generated when we
- 1:44:34are running our application locally.
- 1:44:37So we are going to add them in docker
- 1:44:40ignore. It is similar to git ignore like
- 1:44:42for github repositories you have git
- 1:44:44ignore right? So, GitHub will not pick
- 1:44:46up those files for you. Using Docker
- 1:44:49ignore, we are also doing the same. All
- 1:44:52right. So, that is taken care of. Let me
- 1:44:55create a Docker file here. As you know,
- 1:44:58this is also a simple Python project,
- 1:45:00right? This is also I wouldn't call it
- 1:45:02simple, but this is also a Python
- 1:45:05project. So, we can start with from
- 1:45:10and the base image could be Python. So
- 1:45:13it could be
- 1:45:15python
- 1:45:173.12
- 1:45:19slim same one that we used earlier and
- 1:45:23then
- 1:45:25just like in the previous section here
- 1:45:28as well we are using UV so we can copy
- 1:45:32the UV image directly from GitHub
- 1:45:34container repository. That's what I'm
- 1:45:37going to do. So I'm going to use this
- 1:45:39copy instruction to pull the image of UV
- 1:45:42from GitHub container repository and
- 1:45:45then place it in our user local bin UV.
- 1:45:48Then there are a few environment
- 1:45:52variables that we need to add. Before
- 1:45:54that let me create our work div and the
- 1:45:56workde is going to be app and I need to
- 1:45:59pass in some environment variables.
- 1:46:01Okay, let me write env.
- 1:46:04And then I can provide the key and
- 1:46:06value. The first one will be HF
- 1:46:11home. What this environment variable
- 1:46:13will do is you see any kind of model
- 1:46:18that we are going to use. We will
- 1:46:20download it from hugging face. Okay,
- 1:46:22this is hugging face and you can see
- 1:46:25this is the exact model that we are
- 1:46:26going to use which is sentence
- 1:46:28transformer all mini LM L6 V2. To
- 1:46:30download this from hugging face we need
- 1:46:33somewhere to store them right once we
- 1:46:35download them we need some place to
- 1:46:37store these models and that's what we
- 1:46:39are specifying here hfome can be equal
- 1:46:43to / models so we'll know that okay our
- 1:46:47model files live here then I can use
- 1:46:49this for a line break and I can define
- 1:46:52my other environment variables so let me
- 1:46:55define my other environment variables
- 1:46:58the next one that I'm going to define
- 1:47:00line is UV link
- 1:47:04mode is equal to I will say copy you see
- 1:47:08I'm also getting this as a suggestion
- 1:47:10what this will do is soon we are going
- 1:47:14to implement a cache for UV because the
- 1:47:17libraries that we will install for our
- 1:47:20models for loading our models for
- 1:47:24running our app those can be really
- 1:47:26heavy for example PyTorch itself self is
- 1:47:30over a GBs right so we do not want to
- 1:47:33install it every time something changes
- 1:47:35in our pyrotoml
- 1:47:38instead what we want to do is let's say
- 1:47:40there are all these dependencies and
- 1:47:43let's say I only updated the version of
- 1:47:45fast API I want that only fast API
- 1:47:50should get installed not all the other
- 1:47:52dependencies that are present here if I
- 1:47:55don't do it then all the other
- 1:47:57dependencies will be installed if I
- 1:47:59Don't add at the cache that we are going
- 1:48:00to do in some time. UV link mode copy
- 1:48:03just tells UV to do not download
- 1:48:06anything from scratch but copy it from
- 1:48:08the cache. So that is done. The next one
- 1:48:11is path that we have been adding earlier
- 1:48:15as well in the previous sections. So
- 1:48:17path will allow us to find whatever
- 1:48:20commands we are going to run. So all the
- 1:48:22dependencies like uvcon will be stored
- 1:48:24in our app.vev V env when we use u sync
- 1:48:27path will just let us find those
- 1:48:30dependencies easily. I've specified my
- 1:48:33environment variables. The next thing
- 1:48:35that I need to do is a heavy job. You
- 1:48:38see that we have learned in previous
- 1:48:41section when we discussed about layer
- 1:48:43caching that docker will cache all the
- 1:48:47layers
- 1:48:48from top to bottom and anytime there is
- 1:48:52some change in some layer it will run
- 1:48:54everything below it from scratch.
- 1:48:57We need to download a model and I can
- 1:49:00show you in hugging face that these
- 1:49:03model files are big. Okay. So you see 90
- 1:49:08MB and we are using a smaller model but
- 1:49:11let's say you use some other embedding
- 1:49:13model like quen or something bigger
- 1:49:16every time you change something in your
- 1:49:18code that model might get downloaded
- 1:49:20just because you place the code to
- 1:49:23download it later. So the way we manage
- 1:49:25this is by downloading the model during
- 1:49:29build time and packaging it together
- 1:49:32with our image. So that is what we are
- 1:49:34going to do. We are going to download
- 1:49:36the model right here itself. So that
- 1:49:40even if we change any dependencies, even
- 1:49:43if we change any code, our model does
- 1:49:45not get downloaded every time we build
- 1:49:48or every time we start our container.
- 1:49:50I'm going to add some code here that
- 1:49:52will download our model using the run
- 1:49:55instruction from docker. And you can see
- 1:49:58this is the code that will help us
- 1:50:02download our model. UV pip install d-
- 1:50:07system hugging face_hub.
- 1:50:10What this is doing is it is asking UV to
- 1:50:14install hugging face hub as a system
- 1:50:17dependency. So you know this will run in
- 1:50:20an isolated system. UV is saying that
- 1:50:23download hugging face hub as a system
- 1:50:25dependency.
- 1:50:27Once it is downloaded run the following
- 1:50:30lines using Python. Okay, these are all
- 1:50:34Python code and using this Python code
- 1:50:37we are going to download our model. So
- 1:50:39you can see from hugging face hub we are
- 1:50:41importing this snapshot download and
- 1:50:44using this snapshot download you can
- 1:50:45download artifacts from hugging face. To
- 1:50:47download any artifact you need to
- 1:50:49provide its full path. This is the full
- 1:50:51path. When I go back to hugging face you
- 1:50:54will see this is the full path. I can
- 1:50:56copy it. Right.
- 1:50:59So we specified the path of our model
- 1:51:02and then I have specified ignore
- 1:51:04patterns. What this is doing is it is
- 1:51:07letting this snapshot download function
- 1:51:11know that I do not want extensions
- 1:51:15that are ending with all of these in my
- 1:51:18snapshot. Let me take you back to
- 1:51:20hugging face. You can see there are all
- 1:51:22of these different model types. You can
- 1:51:25see this ot then there is onx. We are
- 1:51:28saying to snapshot download function
- 1:51:31that I do not want all of that. I only
- 1:51:34need this pi pytorch model bin and model
- 1:51:37safe tensors file because this is what
- 1:51:39we are going to use to load our models
- 1:51:42as we are using pytorch. That is done.
- 1:51:45Our model will now get downloaded. Next
- 1:51:48thing that we can do is we can install
- 1:51:52our dependencies using uv all the other
- 1:51:54dependencies that we have. To do that I
- 1:51:57first need to use copy and I'm going to
- 1:52:00copy my pi project toml. So pi project
- 1:52:03dottoml
- 1:52:05and also
- 1:52:07uv dot lock file. And where am I going
- 1:52:10to copy it? I'm going to copy it in my
- 1:52:13working directory which is app. This is
- 1:52:16done. Now I can write the code to
- 1:52:20install all the dependency. We are again
- 1:52:22going to use the run instruction and we
- 1:52:25are adding some special parameters here.
- 1:52:28So you can see mount is equals to type
- 1:52:31is equals to cache. So we are saying
- 1:52:33mount type is cache. This is the
- 1:52:35parameter that will help us cache all
- 1:52:38our dependencies. So this thing that I
- 1:52:40just mentioned that let's say I'm
- 1:52:42changing fast API only fast API should
- 1:52:44get downloaded. UV will mount a cache
- 1:52:47directory. Okay, this is the cache
- 1:52:49directory by the way. this root/cache/
- 1:52:52uv it will mount this directory on our
- 1:52:55local file system. This will not be
- 1:52:58stored in the package or in the
- 1:52:59container itself. It will be out of the
- 1:53:02container in our file system. So that
- 1:53:04even if we destroy the container or we
- 1:53:07rebuild our image, this is still going
- 1:53:10to be there. And then I'm going to use
- 1:53:12UV sync frozen. Frozen simply means that
- 1:53:16use the log file as it is. do not make
- 1:53:19any updates to this log file. Okay, this
- 1:53:22ensures that we are downloading the same
- 1:53:24dependencies that we tested on during
- 1:53:27our developer work. Then I'm using this
- 1:53:31node flag. This node flag simply says
- 1:53:33that do not download any kind of dev
- 1:53:35dependencies. And then I'm also saying
- 1:53:39no install project. This simply means
- 1:53:42that only install and download the
- 1:53:46dependencies. do not install the project
- 1:53:48itself because we are not ready for it
- 1:53:50because we we do not have the project
- 1:53:52files yet. Okay, again we did this for
- 1:53:55layer caching. So you see module will
- 1:53:58not get downloaded if my pyro.coml files
- 1:54:00are changed.
- 1:54:02Dependencies will not get installed if
- 1:54:05they are already cached. Only if certain
- 1:54:08version changes in the pyro.coml or if
- 1:54:11we change the dependency or if we add a
- 1:54:14new dependency only those new
- 1:54:16dependencies will be added and nothing
- 1:54:18will get installed even if I change
- 1:54:22something in my code. So now I can copy
- 1:54:25all the files from my local file system
- 1:54:29into docker and I hope you remember that
- 1:54:33we have added this docker ignore files.
- 1:54:35So all of these files that we do not
- 1:54:37want to copy will not get copied. So if
- 1:54:41you have any test files that you do not
- 1:54:42want to copy in your image, you can
- 1:54:44mention those files here as well. After
- 1:54:47copy I can specify the command. All
- 1:54:50right. So let me copy the command which
- 1:54:54is also same. I'm just taking reference
- 1:54:58of the previous sections. You can see uv
- 1:55:02con main col app host local host and
- 1:55:05port 801. We have changed the port here
- 1:55:08because we will give uh this port to
- 1:55:12chroma or our other service. With this
- 1:55:15the docker file for CPU is ready. This
- 1:55:18is CPU specific docker file. There are
- 1:55:20some changes that we would need to make
- 1:55:22into this docker file to make it GPU
- 1:55:26compliant. So let me copy this docker
- 1:55:30file. Okay, we are going to copy the
- 1:55:31same docker file here it says
- 1:55:35in the same directory.
- 1:55:38No, I don't want to replace it. So let
- 1:55:40me rename it to
- 1:55:42docker file dot GPU and then I'm just
- 1:55:47going to drop it here in embedding
- 1:55:48service. Okay. So you see we have two
- 1:55:51docker files now. Both have different
- 1:55:53versions. One is for CPU and one is for
- 1:55:56GPU. Let's see what all changes we need
- 1:55:58to make here. The very first change is
- 1:56:02because
- 1:56:03we are going to run this on a GPU. We do
- 1:56:07not want to run it on plain Python.
- 1:56:10Okay. We also need apart from Python, we
- 1:56:12also need some CUDA dependencies.
- 1:56:15Remember CUDA library is a library is a
- 1:56:19system package that will allow us to
- 1:56:22interact with the GPU Nvidia GPUs right
- 1:56:26so we need to use
- 1:56:29a CUDA based system a CUDA based image
- 1:56:33as our base image so that is what I'm
- 1:56:36going to do so you can find these
- 1:56:38PyTorch dependencies these PyTorch base
- 1:56:41images on docker hub and you can use
- 1:56:43them as your base image
- 1:56:46Next thing is u install. All of these
- 1:56:48things are going to remain same but
- 1:56:50there is one environment variable that
- 1:56:52we will add. Okay. So the environment
- 1:56:54variable that we are going to add here
- 1:56:59is
- 1:57:01this one.
- 1:57:03We call it device. Okay. And here the
- 1:57:07device is supposed to be CUDA. Now why
- 1:57:09CUDA? because CUDA specifies that okay,
- 1:57:13I want to run this program on a GPU. All
- 1:57:16right, this is a flag. This is going to
- 1:57:18be a flag for PyTorch or Sentence
- 1:57:22Transformer, any libraries that we are
- 1:57:23using that use the GPU for running this
- 1:57:27application. All right. Also, I'm going
- 1:57:30to use another environment variable
- 1:57:33here, which is UV brake system packages
- 1:57:36equals 1. Which means that if there are
- 1:57:38any system packages that are installed,
- 1:57:40feel free to add on to them or update
- 1:57:44them or whatever based on our pipro.com.
- 1:57:48File. Okay. So, we are done with this.
- 1:57:50We can keep everything else as it is. So
- 1:57:53our implementation of the docker files
- 1:57:57for our embedding service is done and
- 1:58:00I'll tell you for the rack service it is
- 1:58:02very easy.
- 1:58:04Why? Because rack service is not very
- 1:58:08different from all the other services
- 1:58:10that we have used so far. I'm going to
- 1:58:13add a docker file in our rack service as
- 1:58:17well.
- 1:58:20And in this docker file we are going to
- 1:58:23use from this is also python 32
- 1:58:29slim. Yeah this is the one that we are
- 1:58:32going to use and I also need my UV. So
- 1:58:38we can add UV here. I need to specify my
- 1:58:42work there which is going to be app.
- 1:58:45Also app is just for convention. You can
- 1:58:48give it any other name as well. You can
- 1:58:50give it rack or telecom chatbot. Up to
- 1:58:53you. But you have to be consistent at
- 1:58:56other places. All right. I'm also adding
- 1:58:59a few environment variables. For
- 1:59:00example, UV link copy. This is something
- 1:59:02that we saw when we were adding the
- 1:59:06docker file for embedding service and
- 1:59:08also the path. Then we have Python
- 1:59:11unbuffered
- 1:59:13equals 1 which means that I do not want
- 1:59:16Python to wait or buffer while printing
- 1:59:20the log statements. I wanted to print it
- 1:59:23as they occur. This enables you know
- 1:59:25this makes debugging easier.
- 1:59:28And then after our environment variables
- 1:59:32are set we already have UV. So we can
- 1:59:35start installing our dependencies. We
- 1:59:38can use copy
- 1:59:40same pi dot
- 1:59:44terminal uv dot lock and also our
- 1:59:48destination which is dot. Now I can use
- 1:59:51u sync to install all these dependencies
- 1:59:54that we have. So I'm going to use the
- 1:59:58run command and using the run command
- 2:00:00you see we have done the same thing. We
- 2:00:02have used d-mount type cache target is
- 2:00:07root cache uv and same kind of
- 2:00:10configuration that we used earlier. We
- 2:00:13could very well copy the docker command
- 2:00:14and just add uh like remove all the
- 2:00:18model download related stuff and use it.
- 2:00:21So here as well I'm going to copy
- 2:00:26everything that we have all the packages
- 2:00:28that we have into this docker instance
- 2:00:32and then we are using streamllet to
- 2:00:36build our rack chatbot. You can see this
- 2:00:39is going to use streamllet. It has
- 2:00:41streamlit as a dependency. So we are
- 2:00:44going to use the streamlit run command
- 2:00:46in our cmd instruction. So you can see
- 2:00:49cmd run source/app.py
- 2:00:53this is our entry point into the
- 2:00:55application. You can see app.py is our
- 2:00:57entry point into the application. After
- 2:01:00this is done we are providing the server
- 2:01:02address server port and we are keeping
- 2:01:05the server headless equals true. So with
- 2:01:08this our docker files are ready. Next
- 2:01:12thing that we need to do is we are not
- 2:01:15going to run each file one by one like a
- 2:01:17caveman. Instead we are going to utilize
- 2:01:21the technique that we learned in the
- 2:01:22previous section which is we are going
- 2:01:24to create docker compose files so that
- 2:01:28our docker compose files contains the
- 2:01:30list of all the services that we are
- 2:01:34going to run. So I will just name it
- 2:01:37compose dot yl
- 2:01:41and we know the drill how to build our
- 2:01:44compos file. The very first thing that
- 2:01:47we are going to start with is going to
- 2:01:50be the name and the name here is going
- 2:01:54to be telecom
- 2:01:56rag and then I can specify all the
- 2:02:00services that I want here. So I'm going
- 2:02:03to do it quickly because we already saw
- 2:02:06how to write a compos file and then I'm
- 2:02:09not going to write each and every line
- 2:02:11here because we have already seen this
- 2:02:14in the previous section. But I'll
- 2:02:16explain you whatever we are adding right
- 2:02:19here. Okay. So I'm adding the embedding
- 2:02:22service first. And in the build command,
- 2:02:25we are providing the name of the folder
- 2:02:28that we need to build. In the previous
- 2:02:30section, we did not do this. We just
- 2:02:32specified dot because that was the
- 2:02:34folder where our docker file was there.
- 2:02:37But here you see the embedded service is
- 2:02:40inside this embedding service directory.
- 2:02:43And we have our docker file here. Okay.
- 2:02:46So that's what we are going to do here.
- 2:02:48And this compos file is by the way for
- 2:02:51the CPU. That is why we are naming the
- 2:02:53image. This is the image tag that we are
- 2:02:55giving. It is token embeddings colon CPU
- 2:03:00and it can use maximum 3GBs of memory.
- 2:03:03That is what we are specifying in the
- 2:03:04memory limit. Next one that we can build
- 2:03:07is chroma. So I'm just going to name it
- 2:03:11chroma. And this chroma will use the
- 2:03:15image that I'm going to describe as
- 2:03:18follows. And we will also add some
- 2:03:21volume here. So let me specify the
- 2:03:24image.
- 2:03:26Okay, this is the Chroma DB image that
- 2:03:29will be pulled from DockerHub. All
- 2:03:32right, then I'm specifying the volume
- 2:03:34here.
- 2:03:37And with this, my configuration for
- 2:03:39Chroma is ready. Exactly. That's it
- 2:03:43because Chromad is already providing us
- 2:03:45with this image. And the final one which
- 2:03:48is our rag app that is going to be a bit
- 2:03:53longer. So let me go step by step here.
- 2:03:56Okay. Here as well let me add the known
- 2:04:00things that we are anyways going to add.
- 2:04:03You can see the very first thing that we
- 2:04:05need is the build context. You know that
- 2:04:08we want to build this dot / rag app
- 2:04:12folder. That's what I have specified
- 2:04:14here. This is a short hand for providing
- 2:04:16the context. And then I'm giving this
- 2:04:18image the tag of telecom rag app v1. And
- 2:04:24I have specified the memory limit to be
- 2:04:272 GBs. Then we need to map the ports. So
- 2:04:32I can specify the ports here. It is 8501
- 2:04:35col 8501. These are the ports where
- 2:04:38streamllet will be running. And after
- 2:04:41specifying the ports, we also need to
- 2:04:44specify the environment variables that
- 2:04:46we need to pick up for this application.
- 2:04:50And you see that in rag app, we already
- 2:04:53have this env file. And you can see what
- 2:04:57this file contains. I hope you remember
- 2:05:00from the GitHub repo right here that we
- 2:05:04are using grock for inference. Okay? And
- 2:05:08because we are using grock for
- 2:05:09inference, we will need this environment
- 2:05:13variable which is grock API key. So to
- 2:05:16get this environment variable, we need
- 2:05:18to specify from where we will be picking
- 2:05:21up this environment variable. All right.
- 2:05:24So that we can specify using this env
- 2:05:28file. We can also use environment to
- 2:05:30directly provide it uh while running the
- 2:05:33container. But since we already have the
- 2:05:35file, I am adding this environment file
- 2:05:38here. Okay. So in this environment file,
- 2:05:41the very first thing that we need to add
- 2:05:43is the path of where this is stored. And
- 2:05:47this is stored in rag app/
- 2:05:52dot env. We have specified the path and
- 2:05:56we are saying that this is required.
- 2:05:59Okay. If this is not provided then this
- 2:06:02will cause an error. I seem to be
- 2:06:05missing tab here. Okay, our environment
- 2:06:08file is ready. Next thing that we going
- 2:06:11to do is we are going to add some
- 2:06:14environment variables for Chroma. So you
- 2:06:16will see in the source code wherever we
- 2:06:19are ingesting something. So let's say
- 2:06:21wherever we are ingesting our FAQs or
- 2:06:25something like this, we need this chrom
- 2:06:27host and port ID so that the chroma
- 2:06:31client can connect with it. You see we
- 2:06:34have this chromtp client. This needs to
- 2:06:37connect to this host and port. So we
- 2:06:41need to provide that as well. I'm going
- 2:06:45to use all of this environments to
- 2:06:47provide that and we'll discuss this
- 2:06:49environment variable in a while. So see
- 2:06:52we are specifying the embedding host
- 2:06:54which is our embedding service col
- 2:06:5780001. This is the port where our
- 2:06:59embedding service is running. And since
- 2:07:02all of these services will be running in
- 2:07:05our docker environment.
- 2:07:07We can use these docker network namings.
- 2:07:10Okay. So we can use the service name
- 2:07:13itself. The service name is embeddings.
- 2:07:15http/
- 2:07:16embeddings colon the port number where
- 2:07:19it is running as chroma host we can
- 2:07:22specify the name of the service which is
- 2:07:24chroma and as port we can specify 8,000
- 2:07:27because that is where chroma is going to
- 2:07:29run after that we need to provide the
- 2:07:32services that our rag app service
- 2:07:35depends on so we can specify it depends
- 2:07:39on embeddings
- 2:07:41okay and it also depends on
- 2:07:46chroma. All right. So these are the
- 2:07:48services that it depends on and we can
- 2:07:51mention it here. All right. Now we can
- 2:07:55specify the volume. You see we are using
- 2:07:58the volumes only for chroma. So this is
- 2:08:00what we can specify here as well. Okay.
- 2:08:04So in my volumes I am using chroma data.
- 2:08:09So let me copy the name chroma data.
- 2:08:11Okay. And this has to be a mapping. So
- 2:08:13colon with this our compos file is ready
- 2:08:16for the CPU. Let me keep it open
- 2:08:19actually and I'm going to open up the
- 2:08:21terminal and I'm going to run this
- 2:08:25compose file. So first I need to go to
- 2:08:28this particular directory which is
- 2:08:30telecom rack. This is where our compose
- 2:08:34file is. Now I can use docker compose
- 2:08:39dash
- 2:08:40compose up dash build command.
- 2:08:46You can see it is pulling the chrom
- 2:08:48image. It will complete all the build
- 2:08:50steps and it will start up the
- 2:08:52container. So let's wait for it to
- 2:08:54complete.
- 2:08:55>> A few moments later.
- 2:08:57>> All right. After waiting for a couple of
- 2:08:59minutes, my applications are up and
- 2:09:03running.
- 2:09:04You can see this is the embedding. This
- 2:09:08is the rag app. And we also have our
- 2:09:12chroma with a beautiful logo. The
- 2:09:14applications are all up. So I can go to
- 2:09:16localhost
- 2:09:188501.
- 2:09:20I can follow this link. You see our high
- 2:09:24API already running. And you can see our
- 2:09:26telecom support assistant is ready here.
- 2:09:30Let's test it out with some query. So,
- 2:09:34let's say why my bill is higher. No,
- 2:09:37this is not a good one. Uh, I was
- 2:09:39charged for roaming. How do I enable
- 2:09:41Wi-Fi calling? This seems like a good
- 2:09:43one, but I'm going to tell you this is
- 2:09:45not going to work. We going to discuss
- 2:09:48why, but
- 2:09:50keep watching. I'm sorry I do not have
- 2:09:52enough information in the provided
- 2:09:54resource. This is happening because we
- 2:09:58have built our image but we have not
- 2:10:02ingested the documents that it requires.
- 2:10:05So let me show you in the code itself.
- 2:10:08You can see everything was successful.
- 2:10:12But when I open up and I'm sorry if let
- 2:10:17me close this. when I open up
- 2:10:21my source code here, you can see there
- 2:10:24are these ingest modules that I need to
- 2:10:27run. Okay, these modules that will
- 2:10:33ingest all of the documents that are
- 2:10:35present in my data set. You see the
- 2:10:37telecom guide which is a PDF, FAQs,
- 2:10:40everything that is present in this
- 2:10:42ticket DB. We want all of these to be
- 2:10:45ingested into our vector store which is
- 2:10:47our Chromma DB so that they work. And
- 2:10:50these kind of cases are very common in
- 2:10:51the industry where you have a service
- 2:10:54and you need to ingest or you need to
- 2:10:57add in a few resources or there are some
- 2:11:02data that you need to some seed data
- 2:11:04that you need to upload. For all of
- 2:11:07those cases, we can use the docker
- 2:11:09compose run command. And I'm going to
- 2:11:12show you how. So you saw all the ingest
- 2:11:16files that we have. We are going to run
- 2:11:19them one by one. In the source in the
- 2:11:21ingest, you see FAQ, PDF and tickets. We
- 2:11:25are going to run all of these files one
- 2:11:28by one so that they can ingest the
- 2:11:31relevant data. And I can use this
- 2:11:34particular docker command to do this.
- 2:11:36Here we are saying docker compose run- r
- 2:11:42which means that close this container as
- 2:11:44soon as this command runs. So we are
- 2:11:46temporarily opening up this container
- 2:11:48and then in the rag app use this
- 2:11:53particular command which is python-m
- 2:11:55src.est.faq.
- 2:11:58This will simply run the faq.py in this
- 2:12:01docker environment. Okay. And once this
- 2:12:04file execution completes, it will exit
- 2:12:06out of this environment. So let me
- 2:12:11press enter and run this. Okay, I'm in
- 2:12:14the docker tutorial. So I need to cd
- 2:12:17into
- 2:12:18the telecom rag. And here I can run
- 2:12:22this. All right, you see docker compose
- 2:12:25run
- 2:12:27and this is running. This will ingest
- 2:12:29all the PDFs into my chrom. You see
- 2:12:34everything is stored in chromb. Now
- 2:12:36instead of faq let me clear this. I can
- 2:12:40also run rest of the files. One is
- 2:12:43called tickets. So this will ingest
- 2:12:45everything that is present in the ticket
- 2:12:47db into chromb.
- 2:12:50Okay. Let me run another one which is
- 2:12:54called PDF. Okay. So PDF. This will
- 2:12:59ingest the PDF guide that we had.
- 2:13:02So this may take some time. Okay, it has
- 2:13:06ingested 37 chunks into Chrom. So with
- 2:13:09this you will see that all of the data
- 2:13:14is ingested now and my app should be
- 2:13:18ready to answer me. Let me go back to
- 2:13:21the app. I can clear all the
- 2:13:23conversations
- 2:13:25and when I ask it about how do I enable
- 2:13:29Wi-Fi calling? Now it is giving me the
- 2:13:31exact response from the guides. Okay.
- 2:13:34And if I ask how do I activate
- 2:13:36international roaming? You can see if I
- 2:13:39ask it my phone SIM is not detected
- 2:13:41after a restart. So you can see it is
- 2:13:43answering all these questions based on
- 2:13:45the data that we have provided and
- 2:13:48ingested into the vector DB. Our telecom
- 2:13:51rack chatbot is working. It is also
- 2:13:54deployable now thanks to docker. I hope
- 2:13:57Bruce will be happy.
- 2:14:01All right, but let's not get very happy
- 2:14:03yet because we have only developed the
- 2:14:06docker compos file for our CPU. We are
- 2:14:10not running the model in our GPU. To run
- 2:14:12it in a GPU, we need to create another
- 2:14:15docker compos file that I've already
- 2:14:17created here for you. Let me explain
- 2:14:21what I've done here. I'm going to name
- 2:14:23it compose.gpu.yamel.
- 2:14:26And here this docker file is created to
- 2:14:30override the original one. Okay. So
- 2:14:33whatever service I'm going to override
- 2:14:35through this particular YAML file. I
- 2:14:39need to name the same service right
- 2:14:41here. So you can see I've named it
- 2:14:43embeddings. Let me open up the original
- 2:14:45file side by side as well. You can see
- 2:14:48this service is called embeddings. And
- 2:14:51here as well we have named it embedded.
- 2:14:53But here in the context we are
- 2:14:55specifying that this needs to use the
- 2:14:58docker file GPU that we created. Okay.
- 2:15:02So here is the docker file GPU that we
- 2:15:06have created and it is very specific to
- 2:15:09all the CUDA dependencies that we need
- 2:15:11here. By the way, there is one thing
- 2:15:14that we had to add here which we missed
- 2:15:17when we were updating it when we copy
- 2:15:20pasted the original one and appended it
- 2:15:23to BGPU native. You can see since we are
- 2:15:28already installing torch as a system
- 2:15:31dependency here we need to add no
- 2:15:35install package and specify torch. So
- 2:15:38this will ensure that it is not
- 2:15:40reinstalling torch for us and we have
- 2:15:43also specified that okay use like system
- 2:15:45side packages whatever environment
- 2:15:48you're using for system side packages
- 2:15:50use that environment only. So with these
- 2:15:52two our torch dependency will be
- 2:15:54installed which is really required to
- 2:15:57run our model on a GPU and then we have
- 2:15:59specified that docker file here we are
- 2:16:02naming it telecom embedding col GPU and
- 2:16:05we have increased the memory limit to
- 2:16:076GB because CUDA needs more memory to
- 2:16:10run. Then in the environment we have
- 2:16:12specified the device to be CUDA which
- 2:16:15will ensure that this runs in a GPU and
- 2:16:19we have also provided this deploy
- 2:16:21instruction where we have said that you
- 2:16:26ensure that Nvidia driver and CUDA
- 2:16:29capabilities are present in whatever
- 2:16:32resources this particular image is being
- 2:16:36deployed to. If these resources do not
- 2:16:40meet then this image will fail. Okay,
- 2:16:43this docker file will not start up. So
- 2:16:45with this our compose files are ready
- 2:16:48and let me show you how to run it using
- 2:16:52docker compose. So we are still going to
- 2:16:54use docker compose. We're going to write
- 2:16:57docker compose and we need to specify
- 2:17:00the file that we want to refer here. So
- 2:17:03the file we have two files. one is
- 2:17:06compost.l yl. Okay, compose dot yl and
- 2:17:12the other file that we have so another f
- 2:17:15and another file that we have is compose
- 2:17:18gpu.yamel. Then we can write up and
- 2:17:21specify the build command. So I've
- 2:17:24already run this because this takes a
- 2:17:25lot of time. It will install all the
- 2:17:28cuda dependencies. It will build the
- 2:17:30image again.
- 2:17:32So because of layer caching, this should
- 2:17:34not take more time here. It should just
- 2:17:37use the cacheed layers and it should
- 2:17:41start up my application. You can see my
- 2:17:44application is started now. It is
- 2:17:46loading the weights using PyTorch. It
- 2:17:50has loaded and my embedding service is
- 2:17:52also ready here. And with this, our
- 2:17:55solution is ready to get deployed.
- 2:17:57Congratulations for whatever you have
- 2:17:58built so far. Remember the homework and
- 2:18:00try it out. Okay? Try multi-stage
- 2:18:02builds. Try all the other capabilities
- 2:18:04of Docker that we talked about and
- 2:18:06mention in the comments what you found
- 2:18:08interesting.
- 2:18:14The next morning on the client server,
- 2:18:17Bruce asks the same roaming question and
- 2:18:19gets the right answer. The green drains
- 2:18:22from his face. It works on their machine
- 2:18:25now. Well, any machine with Docker
- 2:18:28really. and Peter's laptop. It gets to
- 2:18:32stay home.
About this transcript
This page contains the full transcript of Docker Tutorial for Beginners by codebasics, generated from the public captions YouTube serves with the video. The transcript has 20,758 words across 2,964 segments, with the original timestamps preserved so you can click any line to jump to that moment in the embedded player.
What you can do with it
Use the transcript to take notes, quote the speaker, build a study guide, generate a summary with ChatGPT or Claude via the YouTube Summary tool, or export it as a timed subtitle file with YouTube to SRT. You can also re-open it in the transcriber to choose a caption track available on the video.
Free YouTube transcript tool
YouTube2Text is a free YouTube transcript generator — no signup, no daily limit. Paste any YouTube link and get the full transcript instantly, with timestamps, click-to-jump, caption language selection, AI prompts for ChatGPT, Claude, and Gemini, and exports to TXT, SRT, VTT, or Markdown.