
Lesson Description
The "Using a Makefile" Lesson is part of the full, Cloud Infrastructure: Startup to Scale course featured in this preview video. Here's what you'd learn in this lesson:
Erik explains how Make or other task runners can streamline developer workflows by packing common CLI commands into a single file with a simplified set of commands. A makefile is added to the project, and commands for building the application and image, pushing the image, deploying the application, running migrations, and more are added to the file.
Transcript from the "Using a Makefile" Lesson
[00:00:00]
>> Erik Reinert: The next thing we're going to do is we're gonna create a makefile. If you've never heard of a makefile before, like I said, the makefile is really, in its purest form, a makefile is meant to make files. Literally. As a matter of fact, in the old school days when you'd build GCC or other GNU tools, what's nice about the makefile is you would say make some binary, but.
[00:00:27]
And what's cool is that if that file exists, the makefile won't build it again. So it has this really neat ability to say, I'm only gonna make things that I need to make, right? However, we want these things to run all of the time. So the thing I kind of want you to note is that we are going to be using a makefile not to make things, but to run commands that do things for us.
[00:00:52]
So we're kind of making our own little script that makes it easy to work with our project. So what I'm going to do first is I'm going to create a new file. I'm going to say touch makefile, right? And then I'm going to open up this file. Now what we're going to do first is we're going to add a whole bunch of variables to the top, right?
[00:01:09]
And I will explain all of this really quickly as I add them. But at the first part, in the first part of a make file, you normally have variables of some sort, right? So make is its own kind of syntax and its own kind of like not really language, but its own way of.
[00:01:25]
It has its own way of writing these configuration files. You might understand what I'm doing just by looking at it, but basically I'm telling MAKE that I have a whole bunch of variables that I want to use in the commands that I'm going to be running. You might look at these and go, wow.
[00:01:42]
Yeah, that's really nice. This makes a lot of this a lot easier. For example, I have my migration directory now. Now I don't have to worry about doing Dash D migrations every time. It's right there and available to me. I have my account ID and my account default region.
[00:01:59]
And what's nice about that is then I can form the AWS ECR domain. I don't even have to worry about storing it or keeping it anywhere. It's right there in my makefile, my git shell. I want to know the latest version of the thing that I'm building, the latest changes So I can actually put that into a variable and use that as well as I want.
[00:02:20]
Not only can I just use normal plain text and use that in this makefile, I can actually tell make to run a command and then get the output of that command and use that as a variable as well, which makes it really nice. I'm sure the mind starts ticking here on how much you could solve and how much time you could save by just writing some kind of file like this to make your life easier.
[00:02:45]
You'll also see that I have my build image, which I will just quickly update so that it says just the normal one. I also have a build tag, and this is a little bit more complex, but the idea here is it's saying, hey, if build tag exists, then use the build tag value.
[00:03:04]
But if it doesn't exist, then use latest. This can help a lot. When you're running commands and you want default values, you want to just say, I just want to run this, but then override it when I need to versus when I don't need to. I've got a little bit more of a complex thing here, which is I needed to get the host name, just the host name, from the postgres URL that we're using.
[00:03:32]
I can use some bash foo to do that, but what's even nicer is I can use that bash foo and then put it into an environment variable. Now I can run commands like Dockerize to check and connect to the host and see if it's running before I actually run my migrations.
[00:03:51]
Then we've got the same thing here with the Dockerize URL, where then I say, okay, well, if I've got the host, if the host is defined, then do that 5432. Otherwise use local host 5432. What's nice about this is now it means that I can even target my database as a parameter when I want.
[00:04:11]
So if I'm doing local development and I just want to connect to my local database, that'll work out of the box. But say I want to actually have Dockerize or something else connect to something remote, I can use the exact same command, but then I can just override the dockerize URL with something else.
[00:04:27]
You'll see a little bit more of that in a moment. The next thing we're going to do is we're going to add what are called targets. These are the actual commands or the sub commands that we want to be able to do with makefile right now. If I do make and I just do build, you'll see.
[00:04:48]
Well, there's no rule to do that. Like you don't have any ability to do that. But that's what I want. I want to do something like make build and that actually run, go build, blah, blah, blah, blah, blah. So how do we do that? Well, what I'll do is I'll go to the make file and then underneath this I will just add a new line and I will say build.
[00:05:11]
This is where I'm adding my targets. What I'm doing is I'm telling make, hey, there is a target or whatever rule, whatever you want to call it, called build. With build, I want you to run this command. If I just save this with everything we have and then I do make build.
[00:05:33]
There we go. I now just built my application. What's cool is because the make file has what's called the default goal set, I can also run make, and it'll run the exact same command, right? So this is how we could start using make to make our lives easier, right?
[00:05:55]
So that's what we're going to do. So I'm going to delete that file really quickly, I'm going to open up and we're going to add some more targets. So what are some things, like if you guys could tell me really quickly, what are some things that we did in the repo that are command based?
[00:06:09]
>> Speaker 3: Pushing your image.
>> Erik Reinert: Pushing your image. Sure. Okay, what else? There's one before pushing that you have to do.
>> Speaker 3: Building.
>> Erik Reinert: Building. Yep, building it as well. Yep.
>> Speaker 2: Running the migration.
>> Erik Reinert: Yep, yep. Running the migration. Yep. Everything you just said, we have to remember the commands of, right?
[00:06:27]
So my rule of thumb that I like to do with a makefile is anything you run that you're gonna run again, put it in the makefile, make it so that it's easy to remember and get back to, right? So one of you said build image. So what we're gonna do is we are going to do build image.
[00:06:52]
And then what I'm gonna do is just because I wanna make sure it's consistent, we're just gonna do this latest. Now if I have, remember, buildX, build platform, build image. We're using our actual value now in the command, so that if I do make build image. There we go.
[00:07:16]
Now I don't have to remember the entire command. I could just use make for pretty much everything that I'm going to be doing. We don't actually have to wait for that. Okay, so let's go ahead and Add the rest of the stuff. So we've got build image. We've got even our build image login command, right?
[00:07:41]
Like that sucked to have to do, right. Why do that twice? Put that in there. We've got buildimage push, right? So why even put that in there, right? No need to do that. As a matter of fact, I'm sorry. This should be build tag. My bad. This should be build tag.
[00:08:01]
Why should this be build tag? Can anyone tell me, instead of latest, why would I use the build tag variable here?
>> Speaker 3: Might not always be latest.
>> Speaker 3: Might not always be latest.
>> Erik Reinert: Exactly. And if it is, or if we want to use latest, we can just use the default value of build tag.
[00:08:21]
So if you look at build tag at the top, it has that weird if statement. And again, like I said, if the environment variable build tag is set, then it will use that, but if it's not, it will use the latest value. So because I'd rather it do that be dynamic instead of me have to do it manually, I'm just going to tell it to use the build tag there as well, right?
[00:08:46]
Can anyone tell me what this means right here? Why is build image log in here? Yep?
>> Speaker 3: This say, [LAUGH] the make file. If that one is already done or hasn't changed, is older, then you don't. Have to do it again.
>> Erik Reinert: Yeah, exactly. So it's basically like a prerequisite.
[00:09:09]
It'll run that command first and say, okay, is there anything we need to do to build image login? Okay, cool. We don't need to do that. Okay, now we'll do build image push. So it's a way to even sequence. So if I really wanted to, I could say like, build image login, Build image and then build image push.
[00:09:26]
And then if I ran that command, it would do all of those things at once. So I only need to do that. So if you have like a series of commands that you're running or you know, in a specific order and you want to make sure that order is consistent, you can add the other targets after the semicolon or after the colon, and it will run that one and then run the next one, and it'll run the next one and then it'll finally run the one that you want.
[00:09:50]
And so because, you know, we're only pushing to ecr, it makes sense to make sure that we log in every time to make sure that it's a successful push. Okay, so we've got push, we've got pull. Right? That's another one. Like, even though it's like a simple symbol command, it's still worth doing.
[00:10:08]
Then we got a big one. I'm going to go into this one a little bit more into detail, but this is a bit of a spoiler. Can anyone tell me by looking at the code what is going on with this command?
>> Speaker 3: It's connecting to the database. Running migrations.
[00:10:24]
>> Erik Reinert: Yep, exactly. It's literally doing that. It's just doing it in the docker file. We want reproducibility, which means that maybe Goose won't always be in my dev environment, or maybe Goose won't always just be readily available in CI, for example. It actually makes a lot more sense to run migrations in the container.
[00:10:48]
Why not? We can put the migration in the container, we can put the installer in the container, and then we can literally even connect to the same database in the container if we want to as well. So, yeah, it can be very valuable to rerun containers when we need to.
[00:11:06]
Again, that's exactly what we're doing here. We're basically saying, hey, we want to use the image itself to run migrations for where we want to. This is more of a complex example of when this can be really valuable, because now I can run migrations locally very quickly. We're going to add a build promote command and then the last couple of things we're going to add are just up or, sorry, down up, which again, you can see up runs down, and then we'll add normal migration commands.
[00:11:44]
So if we're running the migration outside of the container, and if we're running the migration inside of the container, that's a little bit of a spoiler for the CICD work we're going to be adding in the next short moment. And then we will add run, which just runs build or start, which just runs build and then runs our goals.
[00:12:12]
Okay, cool. So I'm gonna save this command really quickly. And then, what I want to do is, I want to make sure everything's working. So I'm going to run make. I'm going to run make Build. I'm going to run make. Build image. Give this a second. Make Down.
[00:12:37]
Make Up. Make. Start. Cool. Make Migrate. Make Migrate. Status. Make, migrate-validate. Oops, see how much faster that is, [LAUGH] than having to remember all those commands over and over and over again. Think about how much more productive your developers are just by simply adding that one file. You just became more productive.
[00:13:11]
Where else could we use a makefile that we may need to add in the future? Like, where else could we run it?
>> Speaker 3: GitHub actions.
>> Erik Reinert: Exactly right. How many of you have written GitHub Actions or other CI pipelines and stuff and then had to be like, okay, let's copy these commands and put them in here.
[00:13:29]
You have consistency now. You can literally run the exact same commands that you do to build an image locally as you do in CI. So there's no longer a confusion of like, well, how's it running in CI versus how's it running locally? You literally just run the exact same commands and it works right out of the box.
[00:13:46]
So that's another big reason why I would use something what's called a task runner. Now makefile isn't really a task runner or there's nerds on the Internet who will argue about it, but they're really called task runners at the end of the day. There's another one out there that I really love called Just, which is a one I'm a big fan of.
[00:14:05]
There's tons of them out there, so you don't have to use make. If somebody's out there like, why don't you use this versus that? Or whatever. It's up to you, man. Make is pretty much on every Linux based operating system. So it just makes it easier to use Make.
[00:14:20]
But if you don't like make, don't use make. All good, don't worry about it.
Learn Straight from the Experts Who Shape the Modern Web
- In-depth Courses
- Industry Leading Experts
- Learning Paths
- Live Interactive Workshops