1

I want to create a container image of my Angular 12 application with the Paketo buildpack for nodejs. I created the application via Angular CLI and coded it away until now I want to deploy it.

To create the container, I followed the instructions in the paketo samples repository and ran this command:

pack build myui --buildpack gcr.io/paketo-buildpacks/nodejs --env "BP_NODE_RUN_SCRIPTS=build" --env "NODE_ENV=development"

This works fine as along as I delete the node_modules directory before running pack.

Now I wanted to test the image locally and tried to start it with

docker run --tty --publish 4200:4200 myui

It seems to start successfully:

✔ Browser application bundle generation complete.

Initial Chunk Files | Names         |      Size
main.js             | main          |   6.14 MB
vendor.js           | vendor        |   4.22 MB
styles.css          | styles        | 159.33 kB
polyfills.js        | polyfills     | 128.67 kB
runtime.js          | runtime       |   6.56 kB

                    | Initial Total |  10.65 MB

Build at: 2021-08-13T10:06:14.282Z - Hash: 5351646d56eaa8873a38 - Time: 15305ms

** Angular Live Development Server is listening on localhost:4200, open your browser on http://localhost:4200/ **


✔ Compiled successfully.
✔ Browser application bundle generation complete.

5 unchanged chunks

Build at: 2021-08-13T10:06:15.412Z - Hash: 149b56c677aa74ba2361 - Time: 577ms

✔ Compiled successfully. 

Now here is the problem:

➜  ~ docker ps
CONTAINER ID   IMAGE     COMMAND              CREATED          STATUS          PORTS                                       NAMES
6a29ce4cdc86   myui      "/cnb/process/web"   11 minutes ago   Up 11 minutes   0.0.0.0:4200->4200/tcp, :::4200->4200/tcp   confident_grothendieck
➜  ~ curl -vvv http://localhost:4200/
*   Trying ::1...
* TCP_NODELAY set
* Connected to localhost (::1) port 4200 (#0)
> GET / HTTP/1.1
> Host: localhost:4200
> User-Agent: curl/7.64.1
> Accept: */*
>
* Empty reply from server
* Connection #0 to host localhost left intact
curl: (52) Empty reply from server
* Closing connection 0
➜  ~

Does anyone know what went wrong?

EDIT: My best guess currently is that ng serve listens on 127.0.0.1:4200 but should be listening on 0.0.0.0:4200 for traffic to go through. But I am not sure where to look for a configuration. In the buildpack? In angular.cli? Somewhere else?

EDIT: As per request, the full output of the pack command:

➜  ui2 git:(master) pack build myui --buildpack gcr.io/paketo-buildpacks/nodejs --env "BP_NODE_RUN_SCRIPTS=build" --env "NODE_ENV=development"
full: Pulling from paketobuildpacks/builder
Digest: sha256:b34eb3b43d6bf20623ad92523e9e1a055289e74a2fa661adee5d1d90e25afd48
Status: Image is up to date for paketobuildpacks/builder:full
full-cnb: Pulling from paketobuildpacks/run
Digest: sha256:691eb052281456ab5b5a62d1a0e5a67bf7246783565bbecd239c5c9cf1bf51ec
Status: Image is up to date for paketobuildpacks/run:full-cnb
latest: Pulling from paketo-buildpacks/nodejs
Digest: sha256:9887bb6b7d0c410ccdf6fa940658dddecfce5ded1ac5696ced608c78649594b3
Status: Image is up to date for gcr.io/paketo-buildpacks/nodejs:latest
===> DETECTING
5 of 8 buildpacks participating
paketo-buildpacks/ca-certificates 2.3.2
paketo-buildpacks/node-engine     0.6.2
paketo-buildpacks/npm-install     0.4.0
paketo-buildpacks/node-run-script 0.1.0
paketo-buildpacks/npm-start       0.3.0
===> ANALYZING
Previous image with name "myui" not found
===> RESTORING
===> BUILDING

Paketo CA Certificates Buildpack 2.3.2
  https://github.com/paketo-buildpacks/ca-certificates
  Launch Helper: Contributing to layer
    Creating /layers/paketo-buildpacks_ca-certificates/helper/exec.d/ca-certificates-helper
Paketo Node Engine Buildpack 0.6.2
  Resolving Node Engine version
    Candidate version sources (in priority order):
                -> ""
      <unknown> -> ""

    Selected Node Engine version (using ): 14.17.5

  Executing build process
    Installing Node Engine 14.17.5
      Completed in 7.96s

  Configuring build environment
    NODE_ENV     -> "development"
    NODE_HOME    -> "/layers/paketo-buildpacks_node-engine/node"
    NODE_VERBOSE -> "false"

  Configuring launch environment
    NODE_ENV     -> "production"
    NODE_HOME    -> "/layers/paketo-buildpacks_node-engine/node"
    NODE_VERBOSE -> "false"

    Writing profile.d/0_memory_available.sh
      Calculates available memory based on container limits at launch time.
      Made available in the MEMORY_AVAILABLE environment variable.

Paketo NPM Install Buildpack 0.4.0
  Resolving installation process
    Process inputs:
      node_modules      -> "Not found"
      npm-cache         -> "Not found"
      package-lock.json -> "Found"

    Selected NPM build process: 'npm ci'

  Executing build process
    Running 'npm ci --unsafe-perm --cache /layers/paketo-buildpacks_npm-install/npm-cache'
      Completed in 23.58s

  Configuring launch environment
    NPM_CONFIG_LOGLEVEL -> "error"

  Configuring environment shared by build and launch
    PATH -> "$PATH:/layers/paketo-buildpacks_npm-install/modules/node_modules/.bin"


Paketo Node Run Script Buildpack 0.1.0
  Executing build process
    Executing scripts
      Running 'npm run-script build'

        > edit@0.0.0 build /workspace
        > ng build

        - Generating browser application bundles (phase: setup)...
        Compiling @angular/core : es2015 as esm2015
        Compiling @angular/cdk/keycodes : es2015 as esm2015
        Compiling @angular/animations : es2015 as esm2015
        Compiling @angular/animations/browser : es2015 as esm2015
        Compiling @angular/cdk/observers : es2015 as esm2015
        Compiling @angular/common : es2015 as esm2015
        Compiling @angular/cdk/collections : es2015 as esm2015
        Compiling @angular/platform-browser : es2015 as esm2015
        Compiling @angular/cdk/platform : es2015 as esm2015
        Compiling @angular/cdk/bidi : es2015 as esm2015
        Compiling @angular/platform-browser/animations : es2015 as esm2015
        Compiling @angular/cdk/a11y : es2015 as esm2015
        Compiling @angular/forms : es2015 as esm2015
        Compiling @angular/flex-layout/core : es2015 as esm2015
        Compiling @angular/cdk/scrolling : es2015 as esm2015
        Compiling @angular/cdk/portal : es2015 as esm2015
        Compiling @angular/cdk/layout : es2015 as esm2015
        Compiling @angular/material/core : es2015 as esm2015
        Compiling @angular/common/http : es2015 as esm2015
        Compiling @angular/cdk/overlay : es2015 as esm2015
        Compiling @angular/flex-layout/extended : es2015 as esm2015
        Compiling @angular/material/button : es2015 as esm2015
        Compiling @angular/flex-layout/flex : es2015 as esm2015
        Compiling @angular/flex-layout/grid : es2015 as esm2015
        Compiling @angular/platform-browser-dynamic : es2015 as esm2015
        Compiling @angular/router : es2015 as esm2015
        Compiling @angular/material/sidenav : es2015 as esm2015
        Compiling @angular/material/snack-bar : es2015 as esm2015
        Compiling @angular/material/toolbar : es2015 as esm2015
        Compiling @angular/material/icon : es2015 as esm2015
        Compiling @angular/flex-layout : es2015 as esm2015
        Compiling @angular/material/tabs : es2015 as esm2015
        ✔ Browser application bundle generation complete.
        ✔ Browser application bundle generation complete.
        - Copying assets...
        ✔ Copying assets complete.
        - Generating index html...
        ✔ Index html generation complete.

        Initial Chunk Files               | Names         |       Size
        main.a6760a1641b4d1eaecd4.js      | main          |    4.50 MB
        styles.8a29e51b15a5b6e5b823.css   | styles        |   90.06 kB
        polyfills.a6c44b054b34d2bec03f.js | polyfills     |   35.96 kB
        runtime.543bd02f52b0afc6ba6a.js   | runtime       | 1015 bytes

        | Initial Total |    4.63 MB

        Build at: 2021-08-16T15:19:15.350Z - Hash: 0958e10b7d5232954d2e - Time: 46738ms

        ./src/app/footer/footer.component.scss - Warning: Module Warning (from ../layers/paketo-buildpacks_npm-install/modules/node_modules/postcss-loader/dist/cjs.js):
        Warning

        (7:3) postcss-preset-env: start value has mixed support, consider using flex-start instead

        ./src/app/home/home.component.scss - Warning: Module Warning (from ../layers/paketo-buildpacks_npm-install/modules/node_modules/postcss-loader/dist/cjs.js):
        Warning

        (84:3) postcss-preset-env: start value has mixed support, consider using flex-start instead

        ./src/app/home/home.component.scss - Warning: Module Warning (from ../layers/paketo-buildpacks_npm-install/modules/node_modules/postcss-loader/dist/cjs.js):
        Warning

        (85:3) postcss-preset-env: start value has mixed support, consider using flex-start instead

        Warning: /workspace/src/app/resize.service.ts depends on 'element-resize-detector'. CommonJS or AMD dependencies can cause optimization bailouts.
        For more info see: https://angular.io/guide/build#configuring-commonjs-dependencies

        Warning: initial exceeded maximum budget. Budget 500.00 kB was not met by 4.14 MB with a total of 4.63 MB.



      Completed in 51.831s

Paketo NPM Start Buildpack 0.3.0
  Assigning launch processes
    web: ng serve

===> EXPORTING
Adding layer 'paketo-buildpacks/ca-certificates:helper'
Adding layer 'paketo-buildpacks/node-engine:node'
Adding layer 'paketo-buildpacks/npm-install:modules'
Adding layer 'paketo-buildpacks/npm-install:npm-cache'
Adding 1/1 app layer(s)
Adding layer 'launcher'
Adding layer 'config'
Adding layer 'process-types'
Adding label 'io.buildpacks.lifecycle.metadata'
Adding label 'io.buildpacks.build.metadata'
Adding label 'io.buildpacks.project.metadata'
Setting default process type 'web'
Saving myui...
*** Images (b9cd591df4be):
      myui
Adding cache layer 'paketo-buildpacks/node-engine:node'
Adding cache layer 'paketo-buildpacks/npm-install:modules'
Adding cache layer 'paketo-buildpacks/npm-install:npm-cache'
Successfully built image myui
user3235738
  • 335
  • 4
  • 22

3 Answers3

4

Paketo now has a Web Servers buildpack designed for use-cases just like this. See the Paketo documentation for steps to containerize Angular, React, Vue.js, or other front end framework applications with the buildpack.

The pack build command you'll probably want is:

pack build myui -b paketo-buildpacks/web-servers \
--env "BP_WEB_SERVER=nginx" \
--env "BP_WEB_SERVER_ROOT=dist" \
--env "BP_WEB_SERVER_ENABLE_PUSH_STATE=true" \
--env "NODE_ENV=development"

This will:

  • set up the buildpack to serve static assets that are compiled into the dist directory during build
  • automatically generate an nginx.conf that enables push state routing, which you likely want for a single-page app
  • automatically produce a streamlined Docker image that doesn't include your bulky node_modules
2

Old Answer

This is the old answer. Leaving it up for historical context only. Use @Frankie Gallina-Jones's answer instead.


I'll prefix my answer by saying, there are multiple ways to do this. Here are the two ways I would suggest:

Local Build

  1. Build locally, like run npm build on your machine.

  2. Add a buildpack.yml file with this:

    staticfile:
      nginx:
        pushstate: true
    

    That will instruct the staticfile buildpack to generate an Nginx configuration with support for pushstate, which you typically want for single-page Javascript apps.

  3. Run pack build myui -b paketo-buildpacks/nginx -b paketo-community/staticfile -p dist/. Where dist/ is the path to the output of your build process from step #1 (i.e. where your static HTML/CSS/Javascript lives).

    Make sure that your dist/ path includes the buildpack.yml file you created in the previous step. This is required for the staticfile buildpack to pass detection. If you see No buildpack groups passed detection when you try to build, this is likely why.

  4. You can then execute docker run -d -e PORT=8080 -p 8080:8080 myui and it'll run your image. The generated nginx.conf expects PORT to be set to some port on which you want it to listen. Just ensure that the port you select matches the argument -p, so docker exposes the same port.

The advantage of building locally is that it is simple and just works with most apps, because the output of the build is just static HTML/CSS/JS and other asset files. The buildpack just adds Nginx and you're off to the races.

Buildpacks Build

  1. Add a buildpack.yml file with this:

    staticfile:
      nginx:
        pushstate: true
    

    That will instruct the staticfile buildpack to generate an Nginx configuration with support for pushstate, which you typically want for single-page Javascript apps.

    Make sure the buildpack.yml file you created is located at the root of your project, the directory from which you run pack build in the next step. This is required for the staticfile buildpack to pass detection. If you see No buildpack groups passed detection when you try to build, this is likely why.

  2. Run pack build myui -b paketo-buildpacks/nodejs --env "BP_NODE_RUN_SCRIPTS=build" --env "NODE_ENV=development" --buildpack paketo-buildpacks/nginx --buildpack paketo-community/staticfile

This will ensure that Node and NPM are installed, and then it will run npm build. This will produce static file resources from your app. Then the Nginx and staticfile buildpack will run to install & configure Nginx to host the static files for your app.

It's similar to building locally but doesn't require you to have Node or NPM installed locally. The buildpacks will install them and build inside of a container.


The reason that I suggest one of these two build processes is because either one will result in a launch container that is small and only contains your static files and Nginx. This is going to be smaller, more efficient, and more production-ready than having Node.js installed using the dev server of your build tools.

Daniel Mikusa
  • 13,716
  • 1
  • 22
  • 28
  • I just tried the local build since it seemed simple. I am getting `ERROR: No buildpack groups passed detection.` though. Full output at https://pastebin.com/S5NMExmL The path `dist/edit` contains the index.html, js files, etc as you suggested. – user3235738 Aug 18 '21 at 08:35
  • OK, did you put the buildpack.yml file in `dist/edit`? that's needed to pass detection. I should have mentioned that in my instructions, sorry. I just updated them to indicate that. – Daniel Mikusa Aug 18 '21 at 12:59
  • Ohh, that did the trick! I am still getting a 404 from nginx, though. I looked into the container with docker exec and found that nginx is configured to serve /workspace/public but all the files are located next to the nginx.conf in /workspace. – user3235738 Aug 18 '21 at 15:13
  • You should probably put all of your files in a folder, so perhaps you want to run `pack build -p dist/` instead? Then you can set `root: edit` and it'll serve everything up that's in the `edit/` directory. https://github.com/paketo-community/staticfile#buildpackyml-configurations – Daniel Mikusa Aug 18 '21 at 17:05
  • With `-p dist/` I am getting that error again: `No buildpack groups passed detection` - as you described earlier. Is there any source that actually describes the parameters? The link you posted is nice to see what's there but doesn't really help when you don't already know what it is. I read `root` is set to `public` by default and since `nginx.conf` sets `/workspace/public` to the html root, I figured that `public` is the `public` from the yml. It looks to me like it gets ignored. – user3235738 Aug 19 '21 at 06:34
  • Ahh, the buildpack.yml must then be copied to `dist/` instead, of course. Got it! Thanks! One day, I would like to understand what's going on there ... – user3235738 Aug 19 '21 at 08:16
  • Yes, correct. The staticfile buildpack expects that buildpack.yml file to exist so it can read the configuration from it. – Daniel Mikusa Aug 19 '21 at 15:50
0

For any who is new to build packs. Just to follow up on @frankie answer. A few things to consider so you do not spend alot of time struggling to get this to work.

  1. You want to set the default paketobuilder after installing. In my case I achieved this by pack config default-builder paketobuildpacks/builder:base link
  2. Remove --host 0.0.0.0 --port 8080 from package.json script as seen in Paketo example link
  3. Lastly, the most important is make sure when you set --env "BP_WEB_SERVER_ROOT=dist" to dist/<app-name>. E.g. using github link from above it would be --env "BP_WEB_SERVER_ROOT=dist/my-project-name". If this is not set, nginx would you return 403 forbidden.
seju
  • 119
  • 1
  • 8