Skip to content

Instantly share code, notes, and snippets.

@alexisakers
Last active December 26, 2022 12:03
Show Gist options
  • Star 28 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save alexisakers/bea6f9b626e71b48ae6065664748bc97 to your computer and use it in GitHub Desktop.
Save alexisakers/bea6f9b626e71b48ae6065664748bc97 to your computer and use it in GitHub Desktop.
How to run Vapor with Docker in Heroku

How to run Vapor with Docker in Heroku

In this tutorial you'll learn how to run a Vapor server in a Docker container on Heroku.

Recently, Heroku added the ability to run Docker images on their Runtime. You can use this system instead of the traditional buildpack-based system for various reasons, mainly for having more control over the environment your server will run on.

Prerequisites

To complete this tutorial, you'll need:

  • An active Heroku account
  • An Heroku app to deploy your server to
  • A Vapor project

Setting up your development environment

Before you can build and deploy your Vapor server in a Docker container, you'll need to set up your Mac.

First, you'll need to install these two software:

Make sure Docker is running (you should see a whale in your Mac's status bar).

Then, if you haven't done it yet, log into the Heroku CLI:

heroku login

The next step is to install the Heroku Container Registry plugin:

heroku plugins:install heroku-container-registry

Finally, you'll need to log into the container registry (you don't have to enter any credentials):

heroku container:login

✅ Your Mac is now set up to build and deploy Docker images to Heroku!

Configuring your project for the Heroku Container Runtime

Now that your Mac is set up, you'll need to configure your Vapor project for the Heroku Container Runtime.

📁 Open a terminal, and type cd path/to/your/Vapor/project to enter the project directory.

1- Specifying the port

According to Heroku's docs, any server ran into the Container runtime must listen for HTTP traffic at $PORT, which is set by Heroku.

We'll need to reflect that requirement in our server's configuration.

As you may know, Vapor uses the Config/servers.json file to read the port it should listen to.

Vapor also supports having multiple servers.json files for multiple environments. Because $PORT will only be set on Heroku (not on your Mac), you'll need to create a servers.json configuration file that will exclusively be used when in production environment (that is, Heroku).

This can be achieved by creating a new file:

touch Config/production/servers.json

This creates the following file:

Config/production/servers.json

Replace the contents of this file with the following JSON:

{
	"default": {
		"port": "$PORT",
		"host": "0.0.0.0",
		"securityLayer": "none"
	}
}

Vapor is now configured for the Heroku container runtime! It will keep using the same old port on your Mac, and will use $PORT on Heroku.

2- Creating the required files

The last step before you can actually build and deploy your server in a Docker container is to create a Dockerfile and a docker-compose.yml.

These files will be used by the Docker daemon to know how to create your container.

You can execute these commands:

touch Dockerfile && touch docker-compose.yml

👏 You've successfully configured your server for the Heroku Container Runtime!

Creating your container

Let's start building your container!

As mentioned earlier, two files are used by Docker to build your container: the Dockerfile and the docker-compose.yml. The former provides Docker with the instructions to build your container, and the latter is used by Heroku to create multiple dynos from your image (web, worker, ...).

1- Dockerfile

The Dockerfile is the "build manifest" of your container. This is where you'll set up your file system, install dependencies, compile your server. It's like a buildpack, except you have full control of the machine (you're root when building your container).

Let's write a Dockerfile for your Vapor server!

The first step is to decide which base image to use. The base image is the OS your Docker container will be built on top of.

In this tutorial, we'll use aleksaubry/swift-docker. It's a base image I've built. It comes with Swift and libcurl with HTTP/2 installed, which is perfect if you want to send notifications to APNs from your server.

aleksaubry/swift-docker comes in several flavors. We'll choose aleksaubry/swift-docker:xenial-3.0.1, which is Ubuntu 16.04 with Swift 3.0.1. Feel free to replace it with the flavor of your choice.

Now that you've chosen the base image, add it to the Dockerfile:

FROM aleksaubry/swift-docker:xenial-3.0.1

Then, we'll need to copy the Vapor project folder that's on your Mac to the container, and to set it as the working directory. I recommand that you put it in the /app folder:

ADD ./ /app
WORKDIR /app

After that, we'll compile your Vapor server:

RUN swift build --config release

Then, add the build folder to your PATH, so that you can launch the executables in it easily:

ENV PATH /app/.build/release:$PATH

As Heroku won't run the container with root privileges, you'll need to make the /app folder accessible to non-root users:

RUN chmod -R a+w /app && chmod -R 777 /app

Then, create a non-root user:

RUN useradd -m myuser
USER myuser

And finally, specify the default command to run when the container is launched (that is, launching your server):

CMD .build/release/App --env=production --workdir="/app"

We set the environment to production, so that our $PORT-ready servers.json is used, and set the working directory to /app, where the server is at.

Here's the full Dockerfile:

FROM aleksaubry/swift-docker:xenial-3.0.1

ADD ./ /app
WORKDIR /app

RUN swift build --config release

ENV PATH /app/.build/release:$PATH

RUN chmod -R a+w /app && chmod -R 777 /app

RUN useradd -m myuser
USER myuser

CMD .build/release/App --env=production --workdir="/app"

If you need to perform additional steps, read the Dockerfile Reference.

2- docker-compose.yml

Finally (!), let's craft the docker-compose.yml.

Replace its content with this:

web:
  build: .
  command: "bash -c 'App --env=production --workdir=/app/'"
  working_dir: /app/
  environment:
    PORT: 8080
  ports:
    - '8080:8080'
shell:
  build: .
  command: bash
  working_dir: /app/
  environment:
    PORT: 8080
  ports:
    - '8080:8080'

The web object corresponds to a web dyno in Heroku. The shell object corresponds to the one-off dyno that is ran when you call heroku run bash.

💪 You're now one step closer to deploying Vapor in the Heroku Container Runtime!

Deploying!

Deploying is super easy!

Just call:

heroku container:push web --app [NAME OF YOUR HEROKU APP]

and

heroku container:push shell --app [NAME OF YOUR HEROKU APP]

This will follow the instructions in the Dockerfile and build your Swift server (this might take a while!)

🎉 Congrats, you have deployed your Vapor server to Heroku using Docker !

A little victory dance

Going further

You can read all the articles/documentation that helped creating this tutorial:

One interesting thing with using Docker+Heroku is that it enables you to easily implement CI/CD for your Vapor server. You can read this to learn more.

You can find me on Twitter if you need help : @leksantoine

-Alex

@jmdoan1
Copy link

jmdoan1 commented Jun 14, 2017

Thanks for the tutorial!

For anyone getting the "Unknown option --config" error, config has been changed to configuration

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