Pixies, part 3: Go Live


Source control

Now it is a good time to add everything to the git repository if you haven’t done that yet. Create a .gitignore file which contains the stuff you don’t want to check in. Her’s mine:

node_modules
build
.vscode

Now check in your project. You know the drill:

git init
git add -A
git commit -m "initial commit"

Heroku

In this module we are going to deploy our frontend application so the whole world can admire our doing-nothing application. Obviously we are going to expand our app later but, I think for now, it is important to get something out so it can be accessed by anybody on Internet by a standard browser. We are going to use Heroku for that. There other options of course, but Heroku has a generous free account and good tooling. So pop to Heroku and create a free account there. Once you created your account and verified your email, install the Heroku command line tools Once installed, navigate to your pixies/frontend directory and login to Heroku:

heroku login

This should pop up your browser asking for your credentials. Now we need to create a new Heroku app associated with our account:

heroku apps:create

Heroku should create an application and give it some silly name I got this:

Creating app... done, ⬢ glacial-island-41741
https://glacial-island-41741.herokuapp.com/ | https://git.heroku.com/glacial-island-41741.git

Your name will be different. Heroku has different ways to deploy your application, depending on the technology you use but one of the most universal is container-based. For that you need a Dockerfile similar to one we have built in the previous module. Heroku requires you to login to the container service as well, to login and push your image do:

heroku container:login
heroku container:push web

You will see that Heroku builds you image and pushes it to its container service. Heroku also hints that you have to release the image. Do that:

heroku container:release web

Now, try to open the app:

heroku open

Unfortunately, it won’t work yet. What happened? Check the logs:

heroku logs

If you dig through the log you may notice that your nginx container failed to stop with some permission error. The reason is that the nginx server tries to listen on the port 80, by default and this makes Heroku unhappy. Heroku passes the port you are supposed to listen in the PORT environment variable and we should honor that.

Fixing the Heroku port issue

First, we need to tell nginx to listen on the port which is stored in the PORT environment variable. The nginx listen on the port as specified in its config, so we need to override its default config. Create a file nginx.conf with the following bare bones config:

server {
    listen $PORT;

    location / {
        root   /usr/share/nginx/html;
        try_files $uri $uri/ /index.html;
    }
}

Strictly speaking, the try_files directive is unnecessary at the moment, but we’ll need it when we add React later. In our Dockerfile we are going to delete the old, default config and copy over our fixed config. In the Dockerfile:

# build stage as before ...
FROM nginx:alpine AS final

COPY --from=build /build/* /usr/share/nginx/html/

RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf.template

As you see

  • First, we delete the old config
  • Next, we copy over nginx.conf over to the file default.conf.template

We are not done yet. Nginx, unfortunately is not smart enough to replace the $PORT with the actual value passed in the environment.What we need to do, is to replace that $PORT string to the actual environment value passed after the Heroku starts our container but before nginx is actually started. At that moment the environment value is already known so we can inject it to the nginx’s config file. How do we do that? Docker, when it starts your container, looks for the CMD instruction which specifies what to binary actually to run. If you don’t provide one in your Dockerfile, it takes it from the base image nginx:alpine in our case which simply starts the nginx. So what we do, we provide our own CMD instruction which points to the shell script we have full control on and where we can do that port replacement work. Create the boot.sh file with the following content:

#!/bin/sh

sed "s/\$PORT/${PORT}/g" < /etc/nginx/conf.d/default.conf.template > /etc/nginx/conf.d/default.conf
nginx -g "daemon off;"

as you see, it is a shell script (analogous to the .bat file if you are on Windows) which runs the sed command and starts nginx afterwards. the sed command looks a little bit cryptic, I must admit, but all it does is to replace the string $PORT in the default.conf.template with the actual value provided in the environment value PORT and saves the result to the actual nginx config file `default.conf.

I must admit, it is not really enjoyable writing these sed commands. I have written and debugged this one once and carry it from one project to another.

The last thing is to make the CMD instruction in our Dockerfile to point to this shell script. The following lines do the trick

# ... as before
COPY boot.sh /
RUN chmod +x /boot.sh && dos2unix /boot.sh
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf.template
CMD ["/boot.sh"]

The full Dockerfile should look like this:

FROM node:alpine AS build

COPY package.json package-lock.json ./
RUN npm install
COPY . .
RUN npm run build

FROM nginx:alpine AS final

COPY --from=build /build/* /usr/share/nginx/html/

COPY boot.sh /
RUN chmod +x /boot.sh && dos2unix /boot.sh
RUN rm /etc/nginx/conf.d/default.conf
COPY nginx.conf /etc/nginx/conf.d/default.conf.template
CMD ["/boot.sh"]

Now try to push it to heroku again. This time it should work:

heroku container:rm web
heroku container:push web
heroku container:release web
heroku open

If you see your Webpack app responding with the “Pixies” title, congratulations! You have deployed your app live.

One last thing

Ok, so we messed around with the CMD directive and the nginx’s port. What if I want still to run my container locally? Well, you can provide the environment variables as the command line parameters. The following stops the container if it is still running, rebuilds the image and starts the container with the right flags

docker rm -f frontend
docker build -t pixies_frontend .
docker run -d --name frontend -p 8080:80 -e PORT=80 pixies_frontend

the Docker command line parameter -e sets the PORT environment value to 80, as we had before. But feel free to change it.

In the next module we are going to make our front application more interesting, by using React.


See also