Chapters

Hide chapters

Server-Side Swift with Vapor

Third Edition - Early Acess 1 · iOS 13 · Swift 5.2 - Vapor 4 Framework · Xcode 11.4

Before You Begin

Section 0: 3 chapters
Show chapters Hide chapters

Section I: Creating a Simple Web API

Section 1: 13 chapters
Show chapters Hide chapters

32. Deploying with Docker
Written by Jonas Schwartz

Note: This update is an early-access release. This chapter has not yet been updated to Vapor 4.

Docker is a popular containerization technology that has made a huge impact in the way applications are deployed. Containers are a way of isolating your applications, allowing you to run multiple applications on the same server.

Using a container, instead of a full-fledged virtual machine, allows your containerized applications to share more of the host machine’s resources. In turn, this leaves more resources for your application to use rather than consuming them to support the virtual machine itself.

Docker can run almost anywhere, so it provides a good way to standardize how your application should run, from local testing to production.

Note: If you need a refresher on Docker terminology — concepts such as containers and images — check out our Docker tutorial at https://www.raywenderlich.com/9159-docker-on-macos-getting-started.

Docker Compose

This chapter will also show you how to use Docker Compose. Docker Compose is a way to specify a list of different containers that work together as a single unit. These containers share the same virtual network, making it simple for them cooperate with each other.

For example, with Docker Compose, you can spin up both your Vapor app and a PostgreSQL database instance with just one command. They can communicate with each other but are isolated from other instances running on the same host.

Setting up Vapor and PostgreSQL for Development

Begin by setting up a simple development configuration to test your app in a Linux environment. To facilitate debugging any problems that arise, this will be a much simpler configuration than you’ll use in production.

Note: This chapter’s sample project is identical to the project at the end of Chapter 21, “Validation.” You may use that or you may continue to use your existing project. To follow this chapter, you’ll need to rename any Dockerfile or docker-compose.yml that currently exists in your project’s main directory. Your main directory is where your project’s Package.resolved file exists.

In the main directory for your project, create a file named Dockerfile and add the following contents:

#1
FROM swift:4.2
#2
WORKDIR /app
#3
COPY . .
#4
RUN swift package clean
RUN swift build -c release
RUN mkdir /app/bin
RUN mv `swift build -c release --show-bin-path` /app/bin
EXPOSE 8080
#5
ENTRYPOINT ./bin/release/Run serve --env local \
  --hostname 0.0.0.0

Dockerfile provides the “recipe” for creating a Docker container for your app. Here’s what this one does:

  1. Use version 4.2 of the “swift” image from the Docker Hub repository as the starting point.
  2. Tell Docker to use /app as its working directory.
  3. Copy your project to the Docker container.
  4. Build your project and move the executable to /app/bin within the container.
  5. Tell Docker how to start the Vapor app.

Next, also in your project’s main directory, create a file named docker-compose.yml and add the following contents:

# 1
version: '3'
# 2
services:
  # 3
  til-app:
    # 4
    depends_on:
      - postgres
    # 5
    build: .
    # 6
    ports: 
      - "8080:8080"
    environment:
      - DATABASE_HOSTNAME=postgres
      - DATABASE_PORT=5432
  # 7
  postgres:
    # 8
    image: "postgres"
    # 9
    environment:
      - POSTGRES_DB=vapor
      - POSTGRES_USER=vapor
      - POSTGRES_PASSWORD=password

  # 10
  start_dependencies:
    image: dadarek/wait-for-dependencies
    depends_on:
      - postgres
    command: postgres:5432

docker-compose.yml specifies the “recipe” for your entire app with all of its dependencies. Here’s what this one does:

  1. Specify the Docker Compose version.
  2. Define the services for this application.
  3. Define a service for the TIL application.
  4. Set a dependency on the postgres service so Docker Compose starts the PostgreSQL container first.
  5. Build the Dockerfile in the current directory. This is the Dockerfile you created earlier.
  6. Make port 8080 accessible on the host system and inject the DATABASE_HOSTNAME environment variable. Docker Compose has an internal DNS resolver. This allows the til-app container to connect to the postgres container with the hostname postgres. Also set the port for the database. You can specify any other environment variable values your app needs here.
  7. Define a service for the PostgreSQL database.
  8. Use the standard postgres image.
  9. Set the necessary environment variables.
  10. Docker starts all containers at once and PostgreSQL takes several seconds to become ready to accept connections. If TILapp starts before PostgreSQL is ready, TILapp will crash. This service provides a way to ensure the database is running before starting your app.

To bring your app to life, enter the following commands in Terminal:

# 1
docker-compose build
# 2
docker-compose run --rm start_dependencies
# 3
docker-compose up til-app

Here’s what this does:

  1. Build the different Docker images.
  2. Run the start_dependencies service to ensure that PostgreSQL is running and ready.
  3. Start your app.

If you receive an error stating the “vapor” database is not found, follow the clean up steps below and retry the commands above and the application should start successfully. This error might occur if you have previous Docker postgres images on your device.

In your browser, visit http://localhost:8080 to verify the app is up and running. When you’re ready to move ahead, press Control-C to stop everything. Then, clean up your development environment by entering the following in Terminal:

docker-compose down
docker volume prune

This shuts down any running containers and removes all containers and network definitions associated with your app. It then cleans up any old Docker storage you can no longer access.

Setting up Vapor and PostgreSQL for Production

There are several changes you can make to your Docker configuration to simplify managing your app in a production environment. In this section, you’ll split your app into a “builder” container and a production image. You’ll also configure the PostgreSQL container to save its database in your host’s file system, making your data persist across changes to your app and its configuration.

In the main directory for your project, create a file named production.Dockerfile and add the following contents:

# 1
FROM swift:4.2 as builder

# 2
RUN apt-get -qq update && apt-get -q -y install \
  tzdata \
  && rm -r /var/lib/apt/lists/*

# 3
WORKDIR /app
# 4
COPY . .
# 5
RUN mkdir -p /build/lib && \
  cp -R /usr/lib/swift/linux/*.so /build/lib
RUN swift build -c release && \
  mv `swift build -c release --show-bin-path` /build/bin

# Production image
# 6
FROM ubuntu:16.04
# 7
RUN apt-get -qq update && apt-get install -y \
  libicu55 libxml2 libbsd0 libcurl3 libatomic1 \
  tzdata \
  && rm -r /var/lib/apt/lists/*
# 8
WORKDIR /app
# 9
COPY --from=builder /build/bin/Run .
COPY --from=builder /build/lib/* /usr/lib/
# You need the next line if your app serves static resources
# from the Public directory
COPY --from=builder /app/Public ./Public
# You need the next line if your app uses Leaf
COPY --from=builder /app/Resources ./Resources

# 10
ENTRYPOINT ./Run serve --env production --hostname 0.0.0.0 \
  --port 8080
  1. Use version 4.2 of the “swift” image from the Docker Hub repository as the starting point. This container is only for building your app and may be deleted once the app is built.
  2. Install the tzdata package, then clean up the working files. This cleanup is a standard operation when building Docker images based on Linux. It reduces the overall size of the image.
  3. Tell Docker to use /app as its working directory.
  4. Copy your project to the Docker container.
  5. Make a build folder and copy the needed Swift run-time support to it. Build your project and move the executable into the build folder.
  6. Base your production image on Ubuntu 16.04.
  7. Install some needed dependencies, then clean up the working files.
  8. Set up same work directory
  9. Copy files from the builder container. If your project doesn’t serve static content or use Leaf, you may drop the appropriate COPY steps.
  10. Tell Docker how to start the Vapor app.

Next, also in your project’s main directory, create a file named docker-compose.production.yml and add the following contents:

# 1
version: '3'
# 2
services:
  # 3
  til-app:
    # 4
    depends_on:
      - postgres
    # 5
    build:
      context: .
      dockerfile: production.Dockerfile
    # 6
    ports:
      - "8080:8080"

    environment:
      - DATABASE_HOSTNAME=postgres
      - DATABASE_PORT=5432
  # 7
  postgres:
    # 8
    image: "postgres"
    # 9
    volumes:
      - ~/postgres-data:/var/lib/postgresql/data
    # 10
    environment:
      - POSTGRES_DB=vapor
      - POSTGRES_USER=vapor
      - POSTGRES_PASSWORD=password

  # 11
  start_dependencies:
    image: dadarek/wait-for-dependencies
    depends_on:
      - postgres
    command: postgres:5432

Here’s what this does:

  1. Specify the Docker Compose version.
  2. Define the services for this application.
  3. Define a service for the TIL application.
  4. Set a dependency on the postgres service so Docker Compose starts the PostgreSQL container first.
  5. Build the project using production.Dockerfile as its recipe.
  6. Inject the DATABASE_HOSTNAME environment variable. Docker Compose has an internal DNS resolver. This allows the til-app container to connect to the postgres container with the hostname postgres. Also set the port for the database. You can specify any other environment variable values your app needs here.
  7. Define a service for the PostgreSQL database.
  8. Use the standard postgres image.
  9. Set up a persistent volume from ~/postgres-data into the container. This causes the data to live in the host system’s file system rather than inside a Docker container and allows it to persist across launches.
  10. Set the necessary environment variables.
  11. Use the same technique as before to ensure that PostgreSQL is ready to accept connections before starting your app.

To bring your app to life, enter the following commands in Terminal:

docker-compose -f docker-compose.production.yml build
docker-compose -f docker-compose.production.yml \
  run --rm start_dependencies
docker-compose -f docker-compose.production.yml up til-app

These commands mirror the ones used in the Development section but they use your new production configuration files.

Where to go from here?

You’ve seen some basic recipes for how to run your app in a Docker environment. Because Docker is so flexible, these recipes only scratch the surface of the possibilities available to you. For example, you might want to allow your app to save uploaded files in the host’s file system. Or, you might want to configure the app to run behind an nginx proxy server to get secure HTTPS access.

Have a technical question? Want to report a bug? You can ask questions and report bugs to the book authors in our official book forum here.
© 2026 Kodeco Inc.