Cloud Native Buildpacks Vs Dockerfiles For Building Container Images
Tags: docker, kubernetes, cloud, devops
Published: 2022-06-22 10:32:51

In the world of containerization and Kubernetes, there are not only so many levels of abstraction but so many ways to build out your production environments. Before using Kubernetes, you must have a container image, and there are several ways to build a container image.

All (or hopefully all) container images are built using the Open Container Initiative (OCI), which is a standard that was set in place by CNCF to ensure all container images follow the same protocol. That way, they can be used everywhere. If we didn’t have OCI, we would most likely have different container image configurations across our Kubernetes clusters.

You may be thinking to yourself *well, Dockerfiles are the standard way of building container images* and you’re absolutely right. However, there’s another (and sometimes more effective) way - Cloud Native Buildpacks.

In this blog post, you’ll learn what exactly Dockerfiles are, what Cloud Native Buildpacks are, and how to create your very own Cloud Native Buildpack in under 5 minutes.

## Quick Rant On Containers And Docker

Containers have been around for a long time. Before Docker, there were things like Linux Containers (LXC). However, they were a bit different than what we know as containers today. In the Linux world before Docker, `cgroups` and `namespaces` were the primary way to build containers. Both of these have been in the Linux kernel since 2008. Without going too far down the rabbit hole, a lot of engineers found working with containers this way to be incredibly complex.

Docker didn’t invent anything new, it simply made containers far more accessible to everyone. Docker abstracted away all of the nuances with an easy-to-use CLI and fewer scalability headaches.

## What Are Dockerfiles

Dockerfiles, simply put, are a way to build a container image. Most container images are built with a Dockerfile. It consists of:

- A base image that you’re building your container image from (think underlying OS)
- What application you want to run on the container image
- The working directory of the app
- What commands are needed to run the application
- What ports are needed for the application

You’re essentially taking an application and compressing it into the smallest form factor possible.

With Dockerfiles, there are several layers needed. The base image, commands used to run the app, ports used, directories that the application will be stored in, and any other commands needed. Depending on the complexity of your app and the expertise of members of your team, Dockerfiles can get complex.

Here’s an example of a Dockerfile, which is building a Go app. The Dockerfile is pulling from a Go base image, which is on Docker Hub. It then specifies what application will be pulled into the container image, commands that the application needs to run, and what port the application listens on.

```jsx
FROM golang:latest

RUN mkdir /app

ADD . /app

WORKDIR /app

RUN go build -o main .

EXPOSE 8080

CMD [ "/app/main" ]
```

## What Are Cloud Native Buildpacks

Cloud Native Buildpacks take your raw code and turn it into OCI-compliant container images, all without you having to write a Dockerfile or any other configuration file. They look at your source code, determine what base container image it should use, and builds the container image with all of the needed dependencies.

Cloud Native Buildpacks are able to abstract away the need to define a Dockerfile by:

- Automatically detecting buildpacks to use
- Creates the artifact (container image) from the source of your application code
- Automatically exports an OCI-compliant container image

Just like with Dockerfiles having multiple layers, which could be looked at as a negative/con, Cloud Native Buildpacks have cons too. For example, the base image, which you’ll learn about in the **Getting Started with Cloud Native Buildpacks** section, is set and configured by outside parties. That means you have to have trust in those parties to ensure the base images are kept up to date. The other reality is a lot of the configurations for container images are abstracted away with Cloud Native Buildpacks, and teams may not like that.

## Which is Better?

There’s no one-size-fits-all answer, but the best way to put it is…

Dockerfiles:

- Can be complex
- Take certain skills on a team
- Require multiple configurations
- Allows teams to truly customize the way their app interacts with the world
- Full control over what’s being used in the container image

Cloud Native Buildpacks:

- Removes a lot of the complexity from your team
- Builds a container image right from source code, abstracting away everything else
- The automatically built container image is OCI-compliant
- Because of the abstraction, teams may not like Cloud Native Buildpacks because of the level of abstraction. They lose a lot of configuration capabilities
- Debugging can be a bit more of a pain

There’s truly no right or wrong blanket answer. It’s all about what a team's comfort level is with Docker.

## Getting Started with Cloud Native Buildpacks

Now that you know what Dockerfiles and Cloud Native Buildpacks are, it’s time to get hands-on and build your own Cloud Native Buildpack. As discussed earlier, one isn’t necessarily better than the other. It all depends on your workflow and the expertise of your team.

To follow along with this section, you can use the `GoWebAPI` project found [here](https://github.com/AdminTurnedDevOps/GoWebAPI).

First, let’s start with the installation. The installation will vary based on Operating System, and you can find the installation guide [here](https://buildpacks.io/docs/tools/pack/).

As an example, if you’re on macOS, you can install `Pack`, the command-line tool with the following Brew package:

`brew install buildpacks/tap/pack`

Once `Pack` is installed, open up the `GoWebAPI` project in the editor of your choosing. For the purposes of this blog post, VS Code will be used.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/0f904rahi5bol1z8ln5c.png) 

Open up the terminal in your code editor and type the following command, which shows you the suggested base container images for your project. One of the cool things about Cloud Native Buildpacks with the `builder` is that it’ll scan what’s in your code project and tell you what image would be best to use for your code.

`pack builder suggest`

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/xm4pqxqw996vf6fiysq3.png) 

In this case, you’ll use the `[gcr.io/buildpacks/builder:v1](http://gcr.io/buildpacks/builder:v1)` container image, which is essentially a pretty vanilla base image that supports .NET, Go, Java, Node.js, and Python.

The `pack build` command is to build the base image and consists of:

- The `build` flag
- The name of the container image, which in this case is `gocloudpack`
- The `--builder` flag which specifies the container base image
- The `--path` which specifies where your code is. Because you have VS Code open with the `GoWebAPI`, you can specify the dot (`.`) which means *current directory*

`pack build gocloudpack --builder [gcr.io/buildpacks/builder:v1](http://gcr.io/buildpacks/builder:v1) --path .`

Once you run the preceding command, you’ll see an output similar to the screenshot below.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/g9xn4h7gcoznxylkli47.png)

To check that the container image was built, you can use Docker commands. For example, `docker image ls` should show you a container image called `gocloudpack`.

You can test that the container image works by running the following command:

```jsx
docker run -p 8080:8080 -tid gocloudpack
```

If you open up a web browser and go to `localhost:8080`, you’ll see that the application is running.

![Image description](https://dev-to-uploads.s3.amazonaws.com/uploads/articles/4amfxuu1a7orxudcad52.png)
 
Congrats! You’ve officially (and successfully) set up your very own Cloud Native Buildpack