Angular 6 for Enterprise:Ready Web Applications
上QQ阅读APP看书,第一时间看更新

Setting up Docker scripts

Now, let's configure some Docker scripts that you can use to automated the building, testing, and publishing of your container. I have developed a set of scripts called npm Scripts for Docker that work on Windows 10 and macOS. You can get the latest version of these scripts at bit.ly/npmScriptsForDocker:

  1. Sign up for a Docker Hub account on https://hub.docker.com/
  2. Create a public (free) repository for your application

Unfortunately, at the time of publication, Zeit doesn't support private Docker Hub repositories, so your only alternative is to publish your container publicly. If your image must remain private, I encourage you to set up an AWS ECS environment as described in Chapter 11, Highly-Available Cloud Infrastructure on AWS. You can keep tabs on the issue by visiting Zeit Now's documentation at zeit.co/docs/deployment-types/docker.

  1. Update package.json to add a new config property with the following configuration properties:
package.json
  ...
  "config": {
"imageRepo": "[namespace]/[repository]",
"imageName": "custom_app_name",
"imagePort": "0000"
}, ...

The namespace will be your DockerHub username. You will be defining what your repository is called during creation. An example image repository variable should look like duluca/localcast-weather. The image name is for easy identification of your container, while using Docker commands such as docker ps. I will call mine just localcast-weather. The port will define which port should be used to expose your application from inside the container. Since we use 5000 for development, pick a different one, like 8080.

  1. Add Docker scripts to package.json by copy-pasting the scripts from bit.ly/npmScriptsForDocker. Here's an annotated version of the scripts that explains each function.

Note that with npm scripts, the pre and post keywords are used to execute helper scripts, respectively, before or after the execution of a given script and scripts are intentionally broken into smaller pieces to make it easier to read and maintain them:

package.json
... "scripts": {
... "predocker:build": "npm run build",
"docker:build": "cross-conf-env docker image build . -t $npm_package_config_imageRepo:$npm_package_version",
"postdocker:build": "npm run docker:tag",
...

npm run docker:build will build your Angular application in pre, then build the Docker image using the docker image build command and tag the image with a version number in post:

package.json
...
"docker:tag": " cross-conf-env docker image tag $npm_package_config_imageRepo:$npm_package_version $npm_package_config_imageRepo:latest",
...

npm run docker:tag will tag an already built Docker image using the version number from the version property in package.json and the latest tag:

package.json
...
"docker:run": "run-s -c docker:clean docker:runHelper",
"docker:runHelper": "cross-conf-env docker run -e NODE_ENV=local --name $npm_package_config_imageName -d -p $npm_package_config_imagePort:3000 $npm_package_config_imageRepo",
...

npm run docker:run will remove any existing, prior version of an image and run the already built image using the docker run command. Note that the imagePort property is used as the external port of the Docker image, which is mapped to the internal port of the image that the Node.js server listens to, port 3000:

package.json
...
"predocker:publish": "echo Attention! Ensure `docker login` is correct.",
"docker:publish": "cross-conf-env docker image push $npm_package_config_imageRepo:$npm_package_version",
"postdocker:publish": "cross-conf-env docker image push $npm_package_config_imageRepo:latest",
...

npm run docker:publish will publish a built image to the configured repository, in this case, Docker Hub, using the docker image push command. First, the versioned image is published, followed by one tagged with latest in post:

package.json
...
"docker:clean": "cross-conf-env docker rm -f $npm_package_config_imageName",
...

npm run docker:clean will remove a previously built version of the image from your system, using the docker rm -f command:

package.json
...
"docker:taillogs": "cross-conf-env docker logs -f $npm_package_config_imageName",
...

npm run docker:taillogs will display the internal console logs of a running Docker instance using the docker log -f command, a very useful tool when debugging your Docker instance:

package.json
...
"docker:open:win": "echo Trying to launch on Windows && timeout 2 && start http://localhost:%npm_package_config_imagePort%",
"docker:open:mac": "echo Trying to launch on MacOS && sleep 2 && URL=http://localhost:$npm_package_config_imagePort && open $URL",
...

npm run docker:open:win or npm run docker:open:mac will wait for 2 seconds and then launch the browser with the correct URL to your application using the imagePort property:

package.json
...
"predocker:debug": "run-s docker:build docker:run",
"docker:debug": "run-s -cs docker:open:win docker:open:mac docker:taillogs" },
...

npm run docker:debug will build your image and run an instance of it in pre, open the browser, and then start displaying the internal logs of the container.

  1. Install two development dependencies that are needed to ensure cross-platform functionality of the scripts:
$ npm i -D cross-conf-env npm-run-all
  1. Customize the pre-build script to execute unit and e2e tests before building the image:
package.json
"predocker:build": "npm run build -- --prod --output-path dist && npm test -- --watch=false && npm run e2e",

Note that npm run build is provided the --prod argument, which achieves two things:
1. Development time payload of ~2.5 MB is optimized down to ~73kb or less
2. The configuration items defined in src/environments/environment.prod.ts is used at runtime

  1. Update src/environments/environment.prod.ts to look like using your own appId from OpenWeather:
export const environment = {
production: true,
appId: '01ffxxxxxxxxxxxxxxxxxxxxxxxxxxxx',
baseUrl: 'https://',
}

We are modifying how npm test is executed, so the tests are run only once and the tool stops executing. The --watch=false option is provided to achieve this behavioras opposed to the development-friendly default continuous execution behavior. In addition npm run build is provided with --output-path dist to ensure that index.html is published at the root of the folder.

  1. Create a new file named Dockerfile with no file-extensions
  2. Implement the Dockerfile, as shown:
Dockerfile
FROM duluca/minimal-node-web-server:8.11.1
WORKDIR /usr/src/app
COPY dist public

Be sure to inspect the contents of your dist folder. Ensure that index.html is at the root of dist. Otherwise ensure that your Dockerfile copies the folder that has index.html at its root.

  1. Execute npm run predocker:build to ensure that your application changes have been successful
  2. Execute npm run docker:build to ensure that your image builds successfully

While you can run any of the provided scripts inpidually, you really only need to remember two of them going forward:

  • npm run docker:debug will test, build, tag, run, tail and launch your containerize app in a new browser window for testing
  • npm run docker:publish will publish the image you just built and test to the online Docker repository
  1. Execute docker:debug in your terminal:
$ npm run docker:debug

You will note that the scripts display errors in the Terminal window. These are not necessarily indicators of a failure. The scripts are not polished, so they attempt both Windows and macOS compatible scripts parallelly, and during a first build, the clean command fails, because there's nothing to clean. By the time you read this, I may have published better scripts; if not, you're more than welcome to submit a pull request.

A successful docker:debug run should result in a new in-focus browser window with your application and the server logs being tailed in the terminal, as follows:

Current Environment: local.
Server listening on port 3000 inside the container
Attenion: To access server, use http://localhost:EXTERNAL_PORT
EXTERNAL_PORT is specified with 'docker run -p EXTERNAL_PORT:3000'. See 'package.json->imagePort' for th
e default port.
GET / 304 12.402 ms - -
GET /styles.d41d8cd98f00b204e980.bundle.css 304 1.280 ms - -
GET /inline.202587da3544bd761c81.bundle.js 304 11.117 ms - -
GET /polyfills.67d068662b88f84493d2.bundle.js 304 9.269 ms - -
GET /vendor.c0dc0caeb147ad273979.bundle.js 304 2.588 ms - -
GET /main.9e7f6c5fdb72bb69bb94.bundle.js 304 3.712 ms - -

You should always run docker ps to check whether your image is running, when it was last updated, or if it is clashing with the existing images claiming the same port.

  1. Execute docker:publish in your terminal:
$ npm run docker:publish

You should observe a successful run in the Terminal window like this:

The push refers to a repository [docker.io/duluca/localcast-weather]
60f66aaaaa50: Pushed
...
latest: digest: sha256:b680970d76769cf12cc48f37391d8a542fe226b66d9a6f8a7ac81ad77be4f58b size: 2827

Over time, your local Docker cache may grow to a significant size, that is, on my laptop, roughly 40 GB over two years. You can use the docker image prune and docker container prune commands to reduce the size of your cache. For more detailed information, refer to the documentation at https://docs.docker.com/config/pruning.

Let's look into an easier way to interact with Docker next.