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.
To complete this tutorial, you'll need:
- An active Heroku account
- An Heroku app to deploy your server to
- A Vapor project
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!
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.
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:
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.
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!
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, ...).
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.
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 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 !
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
Thanks for the tutorial!
For anyone getting the "Unknown option --config" error, config has been changed to configuration