From PM2 to Docker: Automatic Restarts

From PM2 to Docker: Automatic Restarts

Are you having trouble migrating from PM2 to Docker? Or do you want to know if Docker can do the same things as PM2?

In the From PM2 to Docker series, you will see how important features from PM2 translate to their Docker counterparts. You will learn how to deploy your application the Docker way, and boost your confidence in deploying to production environments.

By using Docker, you leverage a robust ecosystem that brings standardisation to deployments. Your application will be able to handle serious production heavy workloads.

This article covers automatic restarts. We’ll go over all the restart policies offered by Docker and how they translate to a PM2 based configuration.

Automatic restarts

PM2 and Docker have different default settings when it comes to keeping your application online. You get out-of-the-box automatic restarts with PM2, whereas with Docker you have to explicitly enable them.

Restart policies

When you start your containerised application, you can choose one of several restart policies provided by Docker. The syntax for Docker CLI is --restart=<policy> and in Docker Compose, the configuration is nested at the service level with restart: <policy>. There are examples for all possible restart policies at the bottom of this section.

Let’s go through each restart policy.

  • no — Do not automatically restart the container when it exits. This restart policy is the default in Docker.

To achieve the same result in PM2, you would use the --no-autorestart flag in the command line or autorestart: false in your configuration file.

  • on-failure[:max-retries] — Only restart the container if it exits with a non-zero code. Optionally, you can pass a limit to the number of restart attempts.

PM2 doesn’t allow you to change the restart behaviour based on the exit code of your application. Whether your app has exited successfully or with an error, PM2 will treat them equally.

The maximum number of restart attempts in PM2 is configured with the --max-restarts flag or max_restarts option in the configuration file.

  • unless-stopped — Always restart the container, regardless of the exit code. On Docker daemon startup (e.g. after a server restart), only start the container if it was already running before.

You cannot set a limit to the number of restart attempts with this policy (and with the always policy). Docker will try to restart your container indefinitely until you manually stop (docker stop) or remove the container (docker rm -f).

The first half of this policy is the default with PM2. For the second half, you have to use a combination of pm2 startup and pm2 save commands. Only then will your currently running applications persist after server restart. By default, PM2 does not restart your applications on server restart.

  • always — Always restart the container, regardless of the exit code. The container will also start when the Docker daemon starts, even if it was in a stopped state before.

This policy is similar to unless-stopped, with the exception that your application container will restart even if it was (manually) stopped before the Docker daemon shut down.

Below are examples of all possible restart policies if you’re using the Docker CLI to start your container:

# Do not automatically restart the container when it exits. This restart policy is the default. Therefore both commands are the same.
docker run app
docker run --restart=no app

# Only restart the container if it exits with a non-zero code.
docker run --restart=on-failure app

# Same as above, but limit the number of restart attempts to 10.
docker run --restart=on-failure:10 app

# Always restart the container, regardless of the exit code. On Docker daemon startup, only start the container if it was already running before.
docker run --restart=unless-stopped app

# Always restart the container, regardless of the exit code. The container will also start when the Docker daemon starts, even if it was in a stopped state before.
docker run --restart=always app

Docker Compose uses a similar syntax since it only passes the options down to Docker CLI. Here are all possible restart policies you can use in a docker-compose.yml configuration file:

# Demonstrating usage of all possible restart policies. You can only choose one per service.
services:
  app:
    # Do not automatically restart the container when it exits. This restart policy is the default, therefore omitting this configuration has the same result.
    restart: "no"

    # Only restart the container if it exits with a non-zero code.
    restart: "on-failure"

    # Same as above, but limit the number of restart attempts to 10.
    restart: "on-failure:10"

    # Always restart the container, regardless of the exit code. On Docker daemon startup, only start the container if it was already running before.
    restart: "unless-stopped"

    # Always restart the container, regardless of the exit code. The container will also start when the Docker daemon starts, even if it was in a stopped state before.
    restart: "always"       

Restart delays

An essential property of automatic restarts is the delay time between restart attempts. In this regard, PM2 and Docker differ significantly in their default behaviour.

Unless configured otherwise, PM2 doesn’t use a delay time between restart attempts. It also doesn’t have a limit on the number of restart attempts. In the default scenario, PM2 will immediately restart your application for an infinite number of restart attempts. You can configure the delay time with --restart-delay and the maximum number of restart attempts with --max-restarts.

With automatic restarts enabled (on-failure, unless-stopped or always restart policy), Docker uses a delay time of 100ms. It also uses an exponential backoff multiplier of 2, doubling the delay time between restart attempts. The first restart will wait for 100ms, second restart 200ms, then 400ms, 800ms, 1600ms, and so on. The delay is reset after a container stays up for at least 10s.

PM2 allows you to configure an exponential backoff with the --exp-backoff-restart-delay flag. You set an initial delay time which will be multiplied by 1.5 after each attempt.

Even though you can’t adjust this configuration further in Docker — unless using an orchestrator like Docker Swarm or Kubernetes — it’s still important to be aware of how it works. By knowing the differences between PM2 and Docker, you’ll avoid being caught by surprise in production environments.

Conclusion

We’ve learned that PM2 will restart your application by default, and Docker requires you to be explicit. We also learned about the different restart policies that Docker offers and how they translate coming from a PM2 setup.

Even though you can’t configure the delay time between restart attempts, Docker gives you sane defaults to work with. Knowing these differences between PM2 and Docker will increase your confidence in deploying to production environments.

Up next, in From PM2 to Docker: Cluster Mode we look at scaling a Node.js application with Docker and how to make use of all available CPU cores.


Conveniently deploying and hosting your full-stack apps shouldn’t cost a fortune

Hosting multiple apps on a PaaS like Heroku can quickly add up in costs. Not to mention the hidden cost of scaling — if one of your apps starts gaining traction, the steep pricing ladder will quickly empty your wallet 💸

A VPS instance is a lot cheaper, but having to configure and maintain a server is a huge pain. You would rather work on your projects instead.

What you really want is cheaper hosting AND the convenience of a PaaS. Imagine a setup that saves you money, requires very little effort to get started and gives you an easy-to-deploy endpoint for your apps ✨

Leave a Reply