DEV Community

loading...
Cover image for Creating a Buggy App (plus Docker)

Creating a Buggy App (plus Docker)

entrptaher profile image Md. Abu Taher 👨‍💻 ・4 min read

Why would you be creating a buggy app intentionally? Well, that's how we can easily learn how to deal with buggy apps. Do not worry, I will use very simple nodejs app with lots of beginner friendly resources.

Requirements

This tutorial assumes,

  • You are a intermediate-beginner.
  • You know a bit of docker/docker-compose, like how to install docker-compose.
  • You know a bit of nodeJS (or any language suitable for backend api).
  • You want the server to auto restart in case anything goes wrong.

If you don't know where to start about docker, docker compose or nodejs, or if you like to refresh your mind, check this awesome post by Tommy May about Step-by-Step Guide to Setup Node With Docker.

Now after reading that, you will feel this post below is just reinventing the wheel, but please bear with me, don't let me spoil the fun.

Our Dummy App

First we create a very simple express application. You can do it without express or any framework. But let's keep it simple.

Initialize the project and install express,

yarn init -y && yarn add express
Enter fullscreen mode Exit fullscreen mode

Create a minimal index.js,

const os = require('os');
const express = require("express");
const app = express();
const port = process.env.PORT || 3000;

app.get("/", (req, res) => res.send({ hostname: os.hostname() }));

app.listen(port, () => console.log(`Example app listening on port ${port}!`));
Enter fullscreen mode Exit fullscreen mode

Now run the app node index.js, call curl localhost:3000 and you will see a response like this,

➜  curl localhost:3000
{"hostname":"my-pc"}
Enter fullscreen mode Exit fullscreen mode

Up until this now should be very basic and anyone should be able to follow.

Fake Bug

Now the thing is, we want to crash the app manually. There can be many reasons for app to crash. Let's fake a bug and assume the program crashed. Add the following before our routes.

// Fake bug
let shouldError = false;
setTimeout(() => (shouldError = true), 30000);
Enter fullscreen mode Exit fullscreen mode

And change the route to this,

app
  .get("/", (req, res) => {
    if (shouldError) return res.sendStatus(500);
    res.send({ hostname: os.hostname() });
  });
Enter fullscreen mode Exit fullscreen mode

Basically if shouldError is true, then the server should freak out with 500 error.

Now if you restart and try to get the response, it will throw this error after 30 seconds. 30 seconds is a pretty long time to wait for a tutorial, please bear with me.

➜  curl localhost:3000
Internal Server Error
Enter fullscreen mode Exit fullscreen mode

We want to quickly restart this in case any of such problem appears, and not to mention the server should be easily deployable.

Docker and Docker-compose

Lets create our minimal Dockerfile.

FROM node:8

# cd into /app
WORKDIR /app

# copy package.json into app folder
COPY package.json /app

# Install dependencies
RUN npm install

COPY . /app

CMD node index.js
Enter fullscreen mode Exit fullscreen mode

Let's create our minimal docker-compose.yml file.

version: "3"

services:
  express:
    build: .
    ports:
      - "3000:3000" # so we can access it from our host
Enter fullscreen mode Exit fullscreen mode

Yes, just that! Now we can run and have fun with docker, let's run docker-compose in background.

docker-compose up --build -d
Enter fullscreen mode Exit fullscreen mode

and if we want to check the status,

➜  docker ps --format="table {{.Names}}\t{{.Status}}"
NAMES                STATUS
tutorial_express   Up 5 seconds
Enter fullscreen mode Exit fullscreen mode

Oh wait, what is this --format thing?

This will tell us to show only the name and status as stated on the code.

If we do a curl request, we will see a weird response, the hostname of the container is pretty random.

➜  curl localhost:3000
{"hostname":"75ed41a4ac5e"}
Enter fullscreen mode Exit fullscreen mode

Healthcheck

It will still give us Internal Server Error after ~30 seconds. We need to place a healthchecking doctor right there, let's add the following right beside the ports,

healthcheck:
  test: curl http://127.0.0.1:3000 -s -f -o /dev/null || exit 1
  interval: 10s
  timeout: 10s
  retries: 3
Enter fullscreen mode Exit fullscreen mode

This will check the server with specific port and mark the container as unhealthy if the server does not return a OK status code.

Let's check the status around 50 seconds later. It will do a check every 10 seconds and wait for at most 10 seconds to mark the container as healthy or unhealthy.

NAMES                 STATUS
tutorial_express_1    Up 57 seconds (unhealthy)
Enter fullscreen mode Exit fullscreen mode

It's getting pretty boring to type out docker ps every time to see the condition. We can use watch to keep it running on another terminal. Just wrap the command into quotes.

watch 'docker ps --format="table {{.Names}}\t{{.Status}}"'
Enter fullscreen mode Exit fullscreen mode

You will realize it checks for health but does not restart the process. Huh!

Let's make it restart itself if it becomes unhealthy. We will add the willfarrell/autoheal image.

version: "3"

services:
  express:
    build: .
    ports:
      - "3000:3000"
    restart: always # <-- add a pretty restart here
    healthcheck:
      test: curl http://127.0.0.1:3000 -s -f -o /dev/null || exit 1
      interval: 10s
      timeout: 10s
      retries: 3
  autoheal: # <-- Our healing expert
    restart: always
    image: willfarrell/autoheal
    environment:
      - AUTOHEAL_CONTAINER_LABEL=all
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
Enter fullscreen mode Exit fullscreen mode

Alright, now once we restart, we will see it's checking for health and restarting anytime it's marked unhealthy. Just running the following will restart any container that needs restarting.

# re start the container
docker-compose up -d --build

# watch the changes if you are not doing already
watch 'docker ps --format="table {{.Names}}\t{{.Status}}"'
Enter fullscreen mode Exit fullscreen mode

Here is the healthcheck in process, please wait for a while,

Healthcheck

That was a long journey for a beginner. Now you may have some questions like, why are we returning the hostname?, we will see more about that part 2 soon. We will go over simplest example of traefik and deploy to make it more fun.

Stay tuned!

Discussion (0)

pic
Editor guide