-
Notifications
You must be signed in to change notification settings - Fork 254
Description
🗺️ (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 buildRun the Services
docker compose -f hummingbird-docker-compose.yaml up7. 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/todosConclusion
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!