Create and containerize an Angular + Node Application using Buildpacks 1 xCuaPb0QZ5ZHDtFObQVrsA

Create and containerize an Angular + Node Application using Buildpacks

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.

Create and containerize an Angular + Node Application using Buildpacks 1*vjnwt S7KH8vUbVndUH4jw

The article illustrates a Gitpod workspace in which a fresh Angular application is created with a NodeJS backend. Buildpack is installed in the workspace and is used to produce a Container Image for the application. Subsequently, a container is started from this image to demonstrate it works as expected.

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

Executing the steps

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

Create and containerize an Angular + Node Application using Buildpacks 1*Ly4pPm9pY1UTyD9l8iNdyA

Create a new empty repository in GitHub

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

Create and containerize an Angular + Node Application using Buildpacks 1*erwFcC8aOzcEhP bMeGWXQ

Open a fresh Gitpod workspace for the GitHub repository

Click on Continue to launch and enter the workspace:

Create and containerize an Angular + Node Application using Buildpacks 1*g

Click on Continue to launch 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.

Create and containerize an Angular + Node Application using Buildpacks 1*1BfnbiG YOZR2qsIv7nf9Q

The VS Code in the browser that presents the Gitpod workspace

Step 3: Install the Angular CLI

npm install -g @angular/cli

Create and containerize an Angular + Node Application using Buildpacks 1*JBNaGlh8SiEllvw2XB k2g

Install the Angular CLI

Step 4: Create a new Angular application

ng new my-angular-app  

Create and containerize an Angular + Node Application using Buildpacks 1*BsbNQF4kqvhTdFnmvZ3nKg

Creation of a brand new Angular application

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

Create and containerize an Angular + Node Application using Buildpacks

Run the generated Angular application

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:

Create and containerize an Angular + Node Application using Buildpacks 1*DI6DL0C560swK2fBOMN3kg

Inspect the Angular application , exposed at port 4200 (Gitpod create a custom URL that starts with the port-number where our local browser can access the remotely running Angular application)

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.

Create and containerize an Angular + Node Application using Buildpacks 1*Hpydup9Z2uJMKgepBksA3QRun the Node application that starts listening at 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.

Create and containerize an Angular + Node Application using Buildpacks 1*uOaCDUFmctAOvNUhoiCDRAAgain the Angular application, this time served from the Node application (from port 8080 instead of port 4200 that ng serve was listening on)

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

Create and containerize an Angular + Node Application using Buildpacks 1*fmjXccAP9b k1w5LLUFurgThe current set up in the Gitpod workspace with the Node application serving the Angular web application

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

Create and containerize an Angular + Node Application using Buildpacks

Installing the Pack CLI for Buildpacks

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:

Create and containerize an Angular + Node Application using Buildpacks 1*JPG5rck7TlNq0ZoeYdC9QQ

The Builder is retrieved, the source code is inspected and identified and the appropriate container image build steps are executed. The end result is a container image with the built application and the required runtime engines and dependencies

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

Create and containerize an Angular + Node Application using Buildpacks 1*N1vuWne09Br8StserC oOQStarting the container image build process

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

Create and containerize an Angular + Node Application using Buildpacks 1*QXJa6SJGbTDg5wF0BZCIsgFinal logging from the build process

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)

Create and containerize an Angular + Node Application using Buildpacks 1*KfEPM5e5VBh9glAklc0u0A

Run a container from the freshly built container image — listening at port 8081

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

Create and containerize an Angular + Node Application using Buildpacks 1*HMwGDN CLQ0gi9ypHk1OuA

The Angular application, served by the Node application from the Container

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

Create and containerize an Angular + Node Application using Buildpacks

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

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.

Leave a Reply

This site uses Akismet to reduce spam. Learn how your comment data is processed.