Skip to content

Instantly share code, notes, and snippets.

@tuliomonteazul
Last active September 20, 2016 12:23
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save tuliomonteazul/ea81d48584c2a38a68d2 to your computer and use it in GitHub Desktop.
Save tuliomonteazul/ea81d48584c2a38a68d2 to your computer and use it in GitHub Desktop.
How to use Azk, Docker and Nginx for Front-end Development

Using AZK to develop your front-end code and serve with Nginx as a CDN

Motivation

Setting up an envinroment to start coding is hard. Specially if you are in a team and have to share your environment setup. Here is where Azk comes in. It's an orchestrator to setup your development environment. It uses the Azkfile.js to read your project's requirements and install everything using Docker containers. If you're familiar with Vagrant, let's say that Azk solves the same problem but with a different approach. Instead of a monolithic, it's based on separation of concerns. We have modular containers that are lighter and allow more possibilites such as sharing setup to deploy to other environments.

When we develop front-end code nowadays, we often need a set of tools for automation. Specially for Single Page Apps. So, we often need to install Grunt, Gulp or Webpack to run CSS processors like Sass or Less and JavaScript compilers like Babel for ES6/7 or JSX for React.

Thereafter, we need a server or a CDN to serve this files for client requests from browsers.

Introduction

In this article, we will learn how to create an Azkfile.js that creates two docker containers. One with a Node image to run our automation routines and another container that serve the generated files through Nginx. The automation example used in this article run a Webpack automation to convert JSX files into JavaScript, CommonJS code format into browser code format and .sass to .css files.

The article ended up getting quite extensive because we will some concepts in details. Even if you never worked with Azk, Webpack, Docker or Nginx it should be easy to follow.

Installing Azk

Installation instructions can be found here: http://docs.azk.io/en/installation/

Azk runs on top of Docker, so it will install it automatically if its not present. If you're running on Mac OS or Windows, VirtualBox will also be installed as it's a prerequisite for Docker in these environments.

Basic Azk Commands

  • azk start - Reads our Azkfile.js and prepares our environment. It downloads any necessary image if needed to start.

  • azk stop - Stop all running systems that were started by the azk start command. It can also stop an specific system if its name is set as a parameter.

Full command list can be found here: http://docs.azk.io/en/reference/cli/

Download sample project

First, let's download the sample project that will represent our front-end codebase.

git clone https://github.com/agendor/react-webpack-sample

The project is structure is very simple:

react-webpack-sample
  |- src #where we write javascript, css and html
  |- build #where webpack will place our compiled code
  package.json

Create our Azkfile

Go to the project's folder and create the Azkfile:

cd react-webpack-sample

touch Azkfile.js

Now open it with your favorite text editor and insert the first container setup:

/**
 * Documentation: http://docs.azk.io/Azkfile.js
 */
systems({
  'app-builder': {
    // Dependent systems
    depends: [],
    // More images:  http://images.azk.io
    image: {"docker": "azukiapp/node:0.12"},
    // Steps to execute before running instances
    provision: [
      "npm install"
    ],
    workdir: "/azk/#{manifest.dir}",
    shell: "/bin/bash",
    command: "npm start",
    mounts: {
      '/azk/#{manifest.dir}': sync("."),
      '/azk/#{manifest.dir}/node_modules': persistent("./node_modules"),
      '/azk/#{manifest.dir}/public/build': persistent("./public/build")
    },
    envs: {
      // Make sure that the PORT value is the same as the one
      // in ports/http below, and that it's also the same
      // if you're setting it in a .env file
      PATH: "/azk/#{manifest.dir}/node_modules/.bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin",
      NODE_ENV: "dev"
    },
  }
});

Now let's go through this bit by bit. ==Line 5:== The container name will be app-builder. This is not your project's name, as it can be composed by multiple containers. This parameter is useful to be used by other system declarations in depends or extends parameters.
==Line 9:== Our app is a Node.js project. This specify that we need an image that has node installed on version 0.12. We could use the official Docker image here, but Azk team has some custom images with improvements in performance and useful packages already built-in.
==Line 11:== Some prerequisites to start our application. As a node app, we have to install its dependencies set in package.json.
==Line 14:== Our project home folder inside the container. The commands npm install and npm start will be run there.
==Line 15:== The shell binary to run when we use the command azk shell.
==Line 16:== The main command that runs after the container is ready. In this case, npm start will call webpack to compile our assets and watch for changes.
==Line 18:== Any changes done in our project files at the host machine will be synced to the container.
==Line 19-20:== The folders node_modules and public are set to be persisted. This make our generated code be shared between containers. And after the machine is rebooted, our modules are still there. ==Line 26:== To add our node_modules folder to the machine PATH so we can use our modules as commands. Attention that this doesn't append the new value to the current value, it overrides. To set this parameter, we append the current machine's PATH in the end of the new value. (You can find the machine's PATH by running azk shell -- echo \$PATH).

Running

Now let's see the magic happening by running:

azk start

If don't have any personal issues with Murphy, you should see the console output:

$ azk start
azk: ↑ starting `app-builder` system, 1 new instances...
azk: ✓ checking `azukiapp/node:0.12` image...
azk: ⇲ downloading `azukiapp/node:0.12` image...
azk: ⇲ comparing registry layers and local layers...
azk: ⇲ pulling 6/24 layers.
     [=================================================] 100% 6/6
azk: ✓ completed download of `azukiapp/node:0.12`

azk: ↻ provisioning `app-builder` system...
azk: ⎘ syncing files for `app-builder` system...

┌───┬─────────────┬───────────┬──────────────┬─────────────────┬───────────────────┐
│   │ System      │ Instances │ Hostname/url │ Instances-Ports │ Provisioned       │
├───┼─────────────┼───────────┼──────────────┼─────────────────┼───────────────────┤
│ ↑ │ app-builder │ 1         │ dev.azk.io   │ -               │ a few seconds ago │
└───┴─────────────┴───────────┴──────────────┴─────────────────┴───────────────────┘

Ok, nice output.

Let's see if azk has run everything as expected:

$ azk logs
app-builder1 2015-10-04T04:31:35.151155881Z
app-builder1 2015-10-04T04:31:35.151226413Z > azk-nginx-example@1.0.0 start /azk/azk-nginx-example
app-builder1 2015-10-04T04:31:35.151239959Z > webpack
app-builder1 2015-10-04T04:31:35.151246214Z
app-builder1 2015-10-04T04:31:36.818118498Z Hash: ab09909e1d435b9963ea
app-builder1 2015-10-04T04:31:36.818173788Z Version: webpack 1.12.2
app-builder1 2015-10-04T04:31:36.818187053Z Time: 1209ms
app-builder1 2015-10-04T04:31:36.818198204Z     Asset       Size  Chunks             Chunk Names
app-builder1 2015-10-04T04:31:36.818208330Z    app.js  374 bytes       0  [emitted]  app
app-builder1 2015-10-04T04:31:36.818218889Z vendor.js     620 kB       1  [emitted]  vendor
app-builder1 2015-10-04T04:31:36.818229714Z    [0] multi vendor 28 bytes {1} [built]
app-builder1 2015-10-04T04:31:36.818240517Z     + 156 hidden modules

It seems that webpack did his job and created an app.js and vendor.js.
Let's try to see their content.

$ ls public
ls: public: No such file or directory

Well, there is no public folder created in our machine.

Remember that '/azk/#{manifest.dir}': sync("."), at line 18 in our Azkfile? It means that any code changed in our host machine will be sent to the container but not the other way around. As our public/build files were created inside the container, we will not see them in our host machine. You could make this happen by changing it to path("."), but this can lead to performance problems as the project size increases. Specially for the webpack watch function that triggers an auto reload.

Just to be sure that webpack created our files, you can check inside the container by yourself:

$ azk shell
$ ls -la public/build/

total 612
drwxr-xr-x 1 1000 999    136 Oct  4 04:51 .
drwxr-xr-x 1 1000 999    102 Oct  4 04:51 ..
-rw-r--r-- 1 1000 999    374 Oct  4 04:51 app.js
-rw-r--r-- 1 1000 999 620224 Oct  4 04:51 vendor.js

Status code 200 (everything is ok). Our files are really there.

The problem now is: How can we serve this files to our host machine and use them?

You probably already know the answer. That guy in the article's title that wasn't mentioned yet. Let's setup Nginx as a CDN to serve these files.

Serve files with Nginx

If you came from Vagrant, the first thing you may think now is: "Fine, let's add Nginx to our current image to serve those compiled files". Yeah, I thought that too.

But Azk follows Docker principles about containers. So, let's create a container for Nginx.

Edit our Azkfile to add another system:

'app-cdn': {
  depends: [],
  image: {"dockerfile": "./docker/app-cdn/Dockerfile"},
  shell: "/bin/bash",
  mounts: {
    '/azk/#{system.name}': sync("./public"),
    '/azk/#{system.name}/build': persistent("./public/build")
  },
  http: {
    domains: [ "#{system.name}.#{azk.default_domain}" ]
  },
  ports: {
    http: "8008/tcp"
  }
}

==Line 3:== As we need to setup our nginx, we are not defining a ready to work image here. Maybe there are other ways to do this, but I chose to have a Dockerfile that will load our image and add our nginx.conf file. Azk allows you to do that by specifying dockerfile instead of docker. We will add this file in the next step.
==Line 6:== We only need to server public folder files.
==Line 7:== Use our build files persisted by app-builder system.
==Line 10:== The domain name we will use to make requests. This will be translated to app-cdn.dev.azk.io.
==Line 13:== Export the port 8008 of the container to a random port controlled by Azk. We will see this port defined in our nginx file on next steps.

To setup Nginx we need an nginx.conf file. Let`s add it:

# first create a folder for it
mkdir -p docker/app-cdn # -p create sub-folders that don't exist

touch docker/app-cdn/nginx.conf

And add it's content:

#App CDN
server {
  listen 8008;
  server_name localhost;

  root /azk/app-cdn;
  index  index.html;

  location /assets {
    alias /azk/app-cdn/build;
  }
}

Now someone need to add this nginx file to our container. Let's do it with Docker.

touch docker/app-cdn/Dockerfile
FROM nginx

COPY ./nginx.conf /etc/nginx/conf.d/app-cdn.conf

Pretty good! Now let's test it!

$ azk start
azk: ↑ starting `app-cdn` system, 1 new instances...
azk: ✓ checking `azkbuild/fe789d5c32-app-cdn:3bdf3e19feddd9c49a6e6ecce055661b470828c5` image...
azk: ⇲ building `azkbuild/fe789d5c32-app-cdn:3bdf3e19feddd9c49a6e6ecce055661b470828c5` image...
azk: ◴ waiting for `app-cdn` system to start, trying connection to port http/tcp...
azk: System `app-builder` already started

┌───┬─────────────┬───────────┬───────────────────────────┬─────────────────────────┬────────────────┐
│   │ System      │ Instances │ Hostname/url              │ Instances-Ports         │ Provisioned    │
├───┼─────────────┼───────────┼───────────────────────────┼─────────────────────────┼────────────────┤
│ ↑ │ app-builder │ 1         │ dev.azk.io                │ -                       │ 18 minutes ago │
├───┼─────────────┼───────────┼───────────────────────────┼─────────────────────────┼────────────────┤
│ ↑ │ app-cdn     │ 1         │ http://app-cdn.dev.azk.io │ 1-80:32770, 1-443:32769 │ an hour ago    │
│   │             │           │                           │ 1-http:32768            │                │
└───┴─────────────┴───────────┴───────────────────────────┴─────────────────────────┴────────────────┘

See that the system has a hostname. Try to do a request to it:

# index page
curl http://app-cdn.dev.azk.io

# request an asset
curl http://app-cdn.dev.azk.io/assets/app.js

Conclusion

That's it! You can open it on your browser to see:

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment