Skip to content

Swift Server Deployment Guide - swift web service w/ PostgresDB in Docker #1238

@heckj

Description

@heckj

🗺️ (Outline)

1. Swift Web Service with PostgreSQL in Docker

Building a Swift Web Service with PostgreSQL in Docker

In this chapter, we explain how to create a Swift web service that connects securely to a PostgreSQL database – both running in separate Docker containers. The example provided uses Docker Compose for local orchestration and demonstrates secure SSL communication between the web service and the database.

Note: We are not using the AWS Docker Compose version in this example since the PostgreSQL service on AWS has not been set up yet. That topic will be covered in the next chapter.


Table of Contents

1. Folder Structure Overview

The example code package has the following folder structure:

  • /eu-central-1-bundle.pem

    Amazon Root Certificate Authority for the eu-central-1 (Frankfurt) region.

    https://docs.aws.amazon.com/AmazonRDS/latest/UserGuide/UsingWithRDS.SSL.html

  • /aws-hummingbird-docker-compose.yaml

    A Docker Compose file for a single web service connecting to an AWS PostgreSQL instance.

    This file is not used in this local example because the AWS PostgreSQL service is not available yet.

  • /hummingbird-docker-compose.yaml

    A Docker Compose file that defines two services:

    • db: PostgreSQL database service
    • hummingbird-todos: Swift web service
  • /postgres-docker

    • Dockerfile: Builds the PostgreSQL service image.
    • **init-ssl.sh:** Initialization script to configure SSL for the PostgreSQL container.
  • /hummingbird-docker

    • Dockerfile: Builds the Swift web service image.
    • Package.swift: Swift project manifest.
    • Source: Source code of the hummingbird project.
    • Tests: Test source files.
  • /generated-ssl-files

    Initially an empty folder used to store the generated certificate files for both PostgreSQL and the web service.


2. Docker Compose Files

aws-hummingbird-docker-compose.yaml

This file defines a single service configuration for the web service connecting to an AWS-hosted PostgreSQL instance:

services:
  hummingbird-todos:
	  platform: linux/amd64
    build:
      context: .
      dockerfile: hummingbird-docker/Dockerfile
    ports:
      - "8080:8080"
    environment:
      DATABASE_HOST: <aws_database_endpoint_here>
      DATABASE_PORT: 5432
      DATABASE_USER: postgres
      DATABASE_PASSWORD: postgres
      DATABASE_NAME: postgres
      ROOT_CERT_PATH: eu-central-1-bundle.pem
    command: ["--hostname", "0.0.0.0", "--port", "8080"]

hummingbird-docker-compose.yaml

This file sets up two services locally: a PostgreSQL database (db) and the Swift web service (hummingbird-todos):

services:
  db:
    build:
      context: .
      dockerfile: postgres-docker/Dockerfile
    ports:
      - "5432:5432"
    restart: always
    environment:
      POSTGRES_USER: postgres
      POSTGRES_PASSWORD: postgres
      POSTGRES_DB: postgres
    healthcheck:
      test: ["CMD-SHELL", "nc -z localhost 5432"]
      interval: 1s
      timeout: 5s
      retries: 10

  hummingbird-todos:
	  platform: linux/amd64
    build:
      context: .
      dockerfile: hummingbird-docker/Dockerfile
    ports:
      - "8080:8080"
    depends_on:
      db:
        condition: service_healthy
    environment:
      DATABASE_HOST: db
      DATABASE_PORT: 5432
      DATABASE_USER: postgres
      DATABASE_PASSWORD: postgres
      DATABASE_NAME: postgres
      ROOT_CERT_PATH: ca.pem
    command: ["--hostname", "0.0.0.0", "--port", "8080"]

3. Dockerfiles and Initialization Scripts

PostgreSQL Dockerfile (postgres-docker/Dockerfile)

This file uses the official PostgreSQL image and installs additional utilities. It also copies the generated SSL certificate files into the container and sets up an initialization script.

# Use the official PostgreSQL image (using the "latest" tag)
FROM postgres:latest

# Update package lists, install netcat-openbsd, and clean up apt cache
RUN apt-get update && apt-get install -y netcat-openbsd && rm -rf /var/lib/apt/lists/*

# Set the working directory
WORKDIR /var/lib/postgresql

# Copy SSL certificate files into /certs/ in the container
COPY generated-ssl-files/server.pem generated-ssl-files/server.key generated-ssl-files/ca.pem /certs/
RUN chown postgres:postgres /certs/* && chmod 600 /certs/server.key

# Copy the SSL initialization script into Docker's entrypoint directory
COPY postgres-docker/init-ssl.sh /docker-entrypoint-initdb.d/
RUN chmod +x /docker-entrypoint-initdb.d/init-ssl.sh

# Expose PostgreSQL's default port
EXPOSE 5432

PostgreSQL SSL Initialization Script (postgres-docker/init-ssl.sh)

This script appends the necessary SSL configuration to PostgreSQL's configuration files.

#!/bin/bash
set -e

# Append SSL configuration to postgresql.conf
echo "ssl = on" >> "$PGDATA/postgresql.conf"
echo "ssl_cert_file = '/certs/server.pem'" >> "$PGDATA/postgresql.conf"
echo "ssl_key_file = '/certs/server.key'" >> "$PGDATA/postgresql.conf"
echo "ssl_ca_file = '/certs/ca.pem'" >> "$PGDATA/postgresql.conf"

# Allow SSL connections only
echo "hostssl all all 0.0.0.0/0 md5" > "$PGDATA/pg_hba.conf"

Swift Web Service Dockerfile (hummingbird-docker/Dockerfile)

This multi-stage Dockerfile builds the Swift web service and prepares a lean final image.

# Build stage using a Swift image with version 6.0.3-noble
FROM swift:6.0.3-noble AS build

WORKDIR /build

# Copy package manifest files for dependency resolution
COPY ./hummingbird-docker/Package.* ./
RUN swift package resolve

# Copy the rest of the Swift project source files and build in release mode
COPY ./hummingbird-docker .
RUN swift build -c release --static-swift-stdlib

WORKDIR /staging
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/Todos" ./
RUN cp "/usr/libexec/swift/linux/swift-backtrace-static" ./
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\\.resources$' -exec cp -Ra {} ./ \\;

# Final stage based on Ubuntu (noble)
FROM ubuntu:noble
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \\
    && apt-get -q update \\
    && apt-get -q dist-upgrade -y \\
    && rm -r /var/lib/apt/lists/*

RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app hummingbird
WORKDIR /app

# Copy compiled files from build stage and set ownership
COPY --from=build --chown=hummingbird:hummingbird /staging /app

# Copy additional SSL certificates needed for the application
COPY ./generated-ssl-files/ca.pem /app/
COPY ./eu-central-1-bundle.pem /app/

# Configure Swift backtraces for debugging
ENV SWIFT_BACKTRACE=enable=yes,sanitize=yes,threads=all,images=all,interactive=no,swift-backtrace=./swift-backtrace-static

USER hummingbird:hummingbird
EXPOSE 8080

ENTRYPOINT ["/app/Todos"]
CMD ["--hostname", "0.0.0.0", "--port", "8080"]

4. Generating Self-Signed Certificates for PostgreSQL

To establish a secure SSL connection between the Swift web service and PostgreSQL, self-signed certificates are required. On macOS, you can use the following OpenSSL commands inside the generated-ssl-files directory:

# Generate CA (Certificate Authority) private key
openssl genpkey -algorithm RSA -out ca.key

# Generate CA certificate (PEM format)
openssl req -new -x509 -key ca.key -out ca.pem -days 365 -subj "/CN=PostgreSQL-CA"

# Generate server private key
openssl genpkey -algorithm RSA -out server.key

# Create a CSR with correct CN (Common Name) & SAN (Subject Alternative Name)
openssl req -new -key server.key -out server.csr -subj "/CN=localhost"

# Sign the server certificate with CA, adding SAN for localhost, db, and 127.0.0.1
openssl x509 -req -in server.csr -CA ca.pem -CAkey ca.key -CAcreateserial -out server.pem -days 365 \
  -extfile <(echo "subjectAltName=DNS:localhost,DNS:db,IP:127.0.0.1")

5. Secure SSL Connection in the Swift Web Service

In the hummingbird source code, full certificate validation is used to ensure a secure SSL connection. While an alternative with disabled certificate verification is provided (and commented out), the recommended approach is to load and use the trusted root certificate.

hummingbird-docker/Sources/Entrypoint.swift file snippet:

...
let env = Environment()
let host = env.get("DATABASE_HOST")!
let port = env.get("DATABASE_PORT", as: Int.self)!
let user = env.get("DATABASE_USER")!
let pass = env.get("DATABASE_PASSWORD")!
let db = env.get("DATABASE_NAME")!
let rootCertPath = env.get("ROOT_CERT_PATH")!

// Disabled verification (not recommended)
// var tlsConfig = TLSConfiguration.makeClientConfiguration()
// tlsConfig.certificateVerification = .none

// Full certificate validation for secure communication
var tlsConfig = TLSConfiguration.makeClientConfiguration()
let rootCert = try NIOSSLCertificate.fromPEMFile(rootCertPath)
tlsConfig.trustRoots = .certificates(rootCert)
tlsConfig.certificateVerification = .fullVerification

let client = PostgresClient(
    configuration: .init(
        host: host,
        port: port,
        username: user,
        password: pass,
        database: db,
        tls: .require(tlsConfig)
    ),
    backgroundLogger: logger
)
...

6. Building and Running the Docker Containers

To build and run the local example that includes both the PostgreSQL database and the Swift web service, use the following Docker Compose commands:

Build the Services

docker compose -f hummingbird-docker-compose.yaml build

Run the Services

docker compose -f hummingbird-docker-compose.yaml up

7. Testing the Web Service

After the containers are up and running, you can validate the service using these curl commands:

Create a Todo

curl -X POST http://localhost:8080/todos \
  -H "Content-Type: application/json" \
  -d '{"title": "Buy groceries", "order": 1}'

List All Todos

curl -X GET http://localhost:8080/todos

Conclusion

This chapter covered the setup and configuration of a Swift web service with a secure PostgreSQL database using Docker. We detailed the file structure, Docker Compose configurations, Dockerfiles, certificate generation, and secure TLS setup in Swift.

The complete source code and examples are available in the docker-service-example.zip file, which you can download at the end of this document.

Stay tuned for the next chapter where we will explore setting up PostgreSQL on AWS and integrating it with our Swift web service!


docker-service-example.zip

Metadata

Metadata

Assignees

No one assigned

    Labels

    documentationImprovements or additions to documentation

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions