This content originally appeared on Level Up Coding – Medium and was authored by Lucas Jellema
It is very easy to turn an application into a container image that you can publish, share and deploy using any container runtime, cloud service or Kubernetes (lookalike). The tool you can use for this is called Buildpacks. And the steps you take are very simple.
In this article I show a straightforward sequence of steps for creating an Angular web application with NodeJS backend. With a single command I produce the container image that contains the application, the dependencies and required runtimes. A container can be started from this image and runs the application.

The steps in this article are as follows:
- create a fresh GitHub repository
- open a Gitpod workspace for this repository
- install the Angular CLI and create a fresh Angular web application (test build & run the application)
- add a NodeJS application as the backend — to serve static resources for the Angular application and handle HTTP requests from the web application (currently none are implemented) (test run the web application through the NodeJS server)
- install the Pack CLI- the Buildpacks client
- generate the container image for the Angular application using Buildpacks
- run a container based on the container image and demonstrate that the Angular application is running
- update the application, reproduce the container image, run a fresh container
Create a new Angular Application in a GitPod Workspace
Note: for the purpose of trying out Buildpacks, I could create any type of application (Java/SpringBoot, Kotlin, Python/Flask, C#/Blazor, ..). As I happened to explore Angular at this time, that is what I arrived at. I could have created the application anywhere — on my laptop, in a Codespace but I happen to work a lot in Gitpod workspaces, so that is the context for this article.
Step 1: create a new repository in GitHub: https://github.com/lucasjellema/my-angular-app-in-container

Step 2: open a Gitpod workspace for this repository. Add the prefix “https:\\gitpod.io\#” to the GitHub Repository URL: https://gitpod.io/#https://github.com/lucasjellema/my-angular-app-in-container

Click on Continue to launch and enter the workspace:

The Gitpod workspace opens in my browser. It presents VS Code in the browser and it runs a virtual machine with various runtimes — such as Java, Python and Node — and tooling such as Docker, Docker Compose, git, npm, pip. maven and more.

Step 3: Install the Angular CLI
npm install -g @angular/cli

Step 4: Create a new Angular application
ng new my-angular-app

The application at this point is not very interesting — but it is interesting to make sure that it runs, using:
cd my-angular-app
ng server

This command will start the built in web server and make the application accessible through port 4200. We can launch a new browser window at that port:

Step 5: Add NodeJS backend to serve the application (and later on handle API calls from the web application)
NodeJS as runtime is already available. In order to add a NodeJS backend that acts as webserver, we need a few things. Add a file server.js in the root of the application:
const express = require('express');
const path = require('path');
const app = express();
// Serve static files from the "dist" directory
app.use(express.static(path.join(__dirname, 'dist/my-angular-app/browser')));
// Route to serve files based on the request
app.get('/:filename', (req, res) => {
const filename = req.params.filename;
const filePath = path.join(__dirname, 'dist/my-angular-app/browser', filename);
res.sendFile(filePath, (err) => {
if (err) {
console.error(`File not found: ${filename}`);
res.status(404).send('File not found');
}
});
});
// Fallback to index.html for Angular routing (if using Angular's Router)
app.get('*', (req, res) => {
res.sendFile(path.join(__dirname, 'dist/my-angular-app/browser/index.html'));
});
// Start the server
const port = process.env.PORT || 8080;
app.listen(port, () => {
console.log(`Server running on port ${port}`);
});
Add the dependency for express to package.json and also change the start script to "start": "node server.js"
{
"name": "my-angular-app",
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "node server.js",
"build": "ng build",
"watch": "ng build --watch --configuration development",
"test": "ng test"
},
"private": true,
"dependencies": {
"express": "^4.17.1",
...
Make sure the dependency is installed:
npm install
Now, run the application using
npm run start
This kicks off the Node application that starts listening for requests on port 8080.

If we open a browser window a browser window at that port, a request is sent to server.js and the result is that the Angular application is served to the browser from this Node application.

Gitpod again creates a custom endpoint that starts with 8080, the port at which the Node application is listening.

Step 6: Install the Buildpacks CLI Pack
The Pack CLI for Buildpacks is installed using:
sudo add-apt-repository ppa:cncf-buildpacks/pack-cli
sudo apt-get update
sudo apt-get install pack-cli

Step 7: Make Buildpacks produce the Container Image for the Angular application
And now the moment you have been waiting for. I have this Angular +NodeJS application. It is easy enough to run it locally. Just kickoff the NodeJS runtime that was pre installed along with the node modules and other dependencies and the application runs. But how to turn it into a stand alone container image that contains all it needs (and not more) and can be shipped and deployed anywhere (containers can run). This is what Buildpacks does for us. It recognizes the technology used in the application, it knows which runtimes to add in the container image and how to build and package the application and how to expose it and run it when the container is started from the image.
I first need to allow access to the Docker daemon (a special step in the Gitpod workspace I believe)
chmod a+w /var/run/docker.sock
and then I can start the generation of the container:
pack build my-angular-app --builder gcr.io/buildpacks/builder:v1
The process that takes place now is visualized in this picture:

Here is a little of the output from this build process:

And here is the final piece of the build process. It takes between 30–90 seconds.

Step 8: Run a container from Angular / Node Application Container Image
As the container image is complete, we can save it to a repositorym, share and ship it and run it. We no longer have to concern ourselves with the contents of the container image. We know it is Node and Angular but to work with it, deploy it and run it, we do not need to know nor care.
A container can be started in the conventional way:
docker run -p 8081:8080 my-angular-app
Docker run followed by the name of the container image — untagged, so from the local registry. And with a port mapping: the port 8080 inside the container is mapped to port 8081 on the outside (because port 8080 on our Ubuntu host is still used by the original Node application)

When we open the browser, we get to see the application (of course), this time served from the browser, at port 8081:

The situation we now see is as shown in this picture:

The application is running twice: once directly on Ubuntu using the Node runtime listening at port 8080 and a second time in the container, listening at port 8081 (mapped internally to port 8080). This second instance can easily be deployed on any container runtime anywhere in the world: it does not have any external dependencies.
Conclusion
Buildpack is very convenient — especially to quickly get an application containerized (where perhaps you do not need a lot of finetuning of the resulting container image and are mainly interested in getting the application running in a container as quickly as possible). For trying out technologies, quickly sharing prototypes and for plain fun, I like it a lot.
Create and containerize an Angular + Node Application using Buildpacks was originally published in Level Up Coding on Medium, where people are continuing the conversation by highlighting and responding to this story.
This content originally appeared on Level Up Coding – Medium and was authored by Lucas Jellema