Learn how to run Swift & Vapor inside a Docker container. Are you completely new to Docker? This article is just for you!
What the heck is Docker?
Docker is a computer program that performs operating-system-level virtualization, also known as "containerization"
Docker is used to run software packages called "containers". Containers are isolated from each other and bundle their own tools, libraries and configuration files; they can communicate with each other through well-defined channels.
All containers are run by a single operating system kernel and are thus more lightweight than virtual machines.
Containers are created from "images" that specify their precise contents. Images are often created by combining and modifying standard images downloaded from public repositories.
It's pretty simple, but it's also a quite complicated technology. Docker is extremely useful if you don't want to spend hours to setup & configure your work environment. Also it helps the software deployment process, so patches, hotfixes and new code releases can be delivered more frequently. That's why it's usually categorized as a DevOps tool. Guess what: you can use Swift right ahead through a single Docker container, you don't even need to install anything else on your computer. 🐳
Docker architecture in a nutshell
There is a nice get to know post about the Docker ecosystem, but if you want to get a detailed overview you should read the Docker glossary. In this tutorial I'm going to focus on images and containers. Maybe a little bit on the hub, engine & machines. 😅
Lightweight and powerful open source containerization technology combined with a work flow for building and containerizing your applications.
Docker images are the basis of containers.
A container is a runtime instance of a docker image.
A tool that lets you install Docker Engine on virtual hosts, and manage the hosts with
A centralized resource for working with Docker and its components.
So just a little clarification: Docker images can be created through Dockerfiles, these are the templates for running containers. Imagine them like "pre-built install disks" for your container environments. You'll get it through the samples. 💾
Docker cheatsheet for beginners
So you want to learn Docker commands, but you don't know where to start? Before I show you some real world examples here is a useful cheatsheet for the Docker CLI. Don't worry you don't have to remember any of these commands yet! Also...
feel free to skip this section, anyway you'll come back later... 😉
Docker machine commands
- Create new:
docker-machine create MACHINE
- List all:
- Show env:
docker-machine env default
eval "$(docker-machine env default)"
docker-machine env -u
eval $(docker-machine env -u)
Docker image commands
docker pull IMAGE[:TAG]
- Build from local Dockerfile:
docker build -t TAG .
- Build with user and tag:
docker build -t USER/IMAGE:TAG .
docker image lsor
- List all:
docker image ls -aor
docker images -a
- Remove (image or tag):
docker image rm IMAGEor
docker rmi IMAGE
- Remove all dangling (nameless):
docker image prune
- Remove all unused:
docker image prune -a
- Remove all:
docker rmi $(docker images -aq)
docker tag IMAGE TAG
- Save to file:
docker save IMAGE > FILE
- Load from file:
docker load -i FILE
Docker container commands
- Run from image:
docker run IMAGE
- Run with name:
docker run --name NAME IMAGE
- Map a port:
docker run -p HOST:CONTAINER IMAGE
- Map all ports:
docker run -P IMAGE
- Start in background:
docker run -d IMAGE
- Set hostname:
docker run --hostname NAME IMAGE
- Set domain:
docker run --add-host HOSTNAME:IP IMAGE
- Map local directory:
docker run -v HOST:TARGET IMAGE
- Change entrypoint:
docker run -it --entrypoint NAME IMAGE
- List running:
docker container ls
- List all:
docker ps -aor
docker container ls -a
docker stop IDor
docker container stop ID
docker start ID
- Stop all:
docker stop $(docker ps -aq)
- Kill (force stop):
docker kill IDor
docker container kill ID
docker rm IDor
docker container rm ID
- Remove running:
docker rm -f ID
- Remove all stopped:
docker container prune
- Remove all:
docker rm $(docker ps -aq)
docker rename OLD NEW
- Create image from container:
docker commit ID
- Show modified files:
docker diff ID
- Show mapped ports:
docker port ID
- Copy from container:
docker cp ID:SOURCE TARGET
- Copy to container
docker cp TARGET ID:SOURCE
- Show logs:
docker logs ID
- Show processes:
docker top ID
- Start shell:
docker exec -it ID bash
Other useful Docker commands
- Log in:
- Run compose file:
- Get info about image:
docker inspect IMAGE
- Show stats of running containers:
- Show version:
How to run Swift in a Docker container?
As I promised in the beginning let me show you how to run Swift under linux inside a Docker container. First of all, install Docker (fastest way is
brew cask install docker), start the app itself (give it some permissions), and pull the "official" Swift Docker image from the cloud by using the
docker pull swift command. 😎
Packaging Swift code into an image
The first thing I'd like to teach you is how to create a custom Docker image & pack all your Swift source code into it. Just create a new Swift project
swift package init --type=executable inside a folder and also make a new
The FROM directive tells Docker to set our base image, which will be the previously pulled "official" Swift Docker image with some minor changes. Let's make those changes right ahead! We're going to add a new WORKDIR that's called /app, and from now on we'll literally work inside that. The COPY command will copy our local files to the remote (working) directory, CMD will run the given command if you don't specify an external command e.g. run shell. 🐚
Now build, tag & finally run the image. 🔨
docker build -t my-swift-image . # build the image docker run --rm my-swift-image # --rm = remove container after exit
Congratulations! You just made your first Docker image & used your first Docker container with Swift! But wait, do I have to re-build every time I change my code?
Editing Swift code inside a Docker container on-the-fly
The first option is that you execute a bash
docker run -it my-swift-image bash and log in to your container so you'll be able to edit Swift source files inside of it & build the whole package by using
swift build (or you can run
swift test if you'd just like to test your app under linux). This method is a little bit inconvenient, because all the Swift files are copied during the image build process so if you would like to pull out changes from the container you have to manually copy everything, also you can't use your favorite editor inside a terminal window.
Second option is to run the original Swift image, instead of our custom one and attach a local directory to it. Imagine that my sources are under the current directory, so I can simply run
docker run --rm -v $(pwd):/app -it swift in order to start a new container with the local content mapped to the remote app directory. Now I can use Xcode, Sublime Text or anything I want to make modifications, and execute my Swift code inside the terminal window by using
swift run. 🏃
Server side Swift projects inside Docker
You can also run a server side Swift application through Docker. Let's create a new Vapor project
vapor new my-project and a brand new Dockerfile:
Now we use the ADD & RUN instructions in order to build our Swift source files during the image build phase. Next we have to build our sources & we'll copy the build artifact into a better location, because we don't want to mess with the architecture name in the executable's build path.
Finally we have to EXPOSE our local 8080 port to the outside world, and set our main ENTRYPOINT to the newly created
Run executable file. We also have to change the IP address that Vapor listens on. Since we are inside of a Docker container we have to use 0.0.0.0 instead of 127.0.0.1.
docker build -t vapor-image . docker run --name vapor-server -p 8080:8080 vapor-image
Let's build our image & run it. You also have to publish your exposed port, in order to make things work. Now if you go to your browser and enter
http://localhost:8080 you'll see that Vapor is running inside your container...
It works! ... like magic! ⭐️
Creating Swift (micro)services using Docker compose
The docker-compose command can be used to start multiple docker containers at once. You can have separate containers for every single service, like your Swift application, or the database that you are going to use. You can deploy & start all of your microservices with just one command. Let me show you how to do this. 🤓
In this example I'm going to connect our Vapor application (running inside a Docker container) to another one that's going to run a PostgreSQL database. In order to make our sample work, we have to switch from SQLite to pgsql inside our project.
First, we have to add postgres as a dependency inside the
Next we change the
configure.swift file in order to set up the PostgreSQL driver instead of SQLite, also we're going to get the database connection details from the environment. Thankfully Vapor has a nice support to get env vars:
Only a slight change is needed inside the
We also have to add one little thing inside the
Run/main.swift file. If a
SLEEP_LENGHT environment variable is present we'll wait before we start our Vapor server. Despite the
docker-compose up command can manage dependencies, it won't wait for services to load completely inside the containers and the PostgreSQL database service needs just a little extra time to boot up. In a production environment you could solve this issue by using health checks, but for the demo sleep is fine enough. 😴
The last thing that we have to do is to create a
docker-compose.yaml file. This contains all the services that we'd like to start with the base images / environment variables and other configurations. Just use the following content as a base:
Ready? Go! Fire up your terminal window and enter
docker-compose up to start all of our services. Docker will first build the api service based on your local Dockerfile and name it as
vapor-composer-image and pull the postgres image from the hub. Next it'll create a container for both services based on the images. Environment variables will be passed to the images (you can reach out to other containers by using the service names) and the api service will be exposed on port 8080. 🌍
http://localhost:8080 after everything is up and runnning. You can stop the services by entering the
docker-compose down command in a new terminal tab or window. You can also "get into the containers" - if you want to run a special script - by executing
docker exec -it <my-container> bash. So cool, isn't it? 😎
🐳 +🐘 +💧 = ❤️
I hope you enjoyed this comprehensive Docker tutorial. If you want to learn more about building & hosting cloud services, you should browse my collection of server side Swift articles. More new Vapor 3 related stuff is on it's way, I promise! 😅