Suggestions for resolving FoundationNetworking Error when static linking on Linux/Docker?

I have a small bit of code (from a dependency) that used to work, but seems to have broken as part of updates to my docker setup (updating to use swift 5.7 nightly toolchain with static linking) and I'm not sure how to resolve it after discussing on the Vapor Discord.

The code looks like the following (from dependency):

do {
  data = try Data(contentsOf: sanitizedSchemeUrl)
} catch {
  return .failure(.internalError(reason: error.localizedDescription))
}

and the top of the file includes the following import:

#if canImport(FoundationNetworking)
import FoundationNetworking
#endif

Now, when calling this code inside of a docker container I get the following error:

Foundation/NSSwiftRuntime.swift:409: Fatal error: You must link or load module FoundationNetworking to load non-file: URL content using String(contentsOf:…), Data(contentsOf:…), etc.

For reference, the full docker file looks as follows:

# ================================
# Build image
# ================================
FROM swiftlang/swift:nightly-5.7-focal as build

# Install dependencies - do this before build for improved docker caching
# 1. libcurl4-openssl-dev needed for libcurl/ccurl commands in app
# 2. libssl-dev required by libcurl4-openssl-dev
# 3. cleanup after apt process
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
  && apt-get -q update \
  && apt-get -q dist-upgrade -y \
  && apt-get -q -y install \
  libcurl4-openssl-dev \
  libssl-dev \
  libxml2-dev \
  && rm -rf /var/lib/apt/lists/*

# Set /app as working directory
WORKDIR /build

# Add the ssh key used to clone private dependencies from github
ARG SSH_PRIVATE_KEY
RUN mkdir /root/.ssh/
RUN echo "${SSH_PRIVATE_KEY}" | base64 -d > /root/.ssh/id_rsa
RUN touch /root/.ssh/known_hosts
RUN ssh-keyscan github.com >> /root/.ssh/known_hosts
RUN chmod 0600 /root/.ssh/id_rsa

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve

# Copy everything from the build directory to docker's working directory
COPY . .

# Build everything, with optimizations
# -g enables debug info in compiled executable
# -Xswiftc passes flag through to all swift compiler invocations
# -c release => build with configuration release
RUN swift build -c release --static-swift-stdlib
#RUN --mount=type=cache,target=/code/.build swift build -c release (for caching builds in non-release configurations)

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/Run" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

# Copy any resouces from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

# ================================
# Run image
# ================================
FROM ubuntu:focal

# Make sure all system packages are up to date.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
  && apt-get -q update \
  && apt-get -q dist-upgrade -y \
  && apt-get -q install -y \
  ca-certificates \
  tzdata \
  libxml2 \
  libcurl4 \
  && rm -r /var/lib/apt/lists/*
    
# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Need to learn what these do
ARG env
ENV env ${env:-production}

ARG GIT_HASH=undefinedHash
ARG GIT_TAG=undefinedTag
ARG GIT_BRANCH=undefinedBranch

ENV GIT_HASH=$GIT_HASH
ENV GIT_TAG=$GIT_TAG
ENV GIT_BRANCH=$GIT_BRANCH

# Sets working directory to /app
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Let Docker bind to port 8080
EXPOSE 8080

ENTRYPOINT ["./Run"]
CMD ["serve", "--env", "$env", "--hostname", "0.0.0.0", "--port", "8080", "--auto-migrate"]

Any help is greatly appreciated.

2 Likes
# ================================
# Run image
# ================================
FROM ubuntu:focal

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y \
      ca-certificates \
      tzdata \
      openssl \
      libssl-dev \
      unzip \
      zip \
      libzip-dev \
      libplist3 \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      libxml2 \
    && rm -r /var/lib/apt/lists/*

Since my English is not good, I can only provide part of my configuration. You can try the above code if it solves your problem

1 Like

@jsam can you create an issue on GitHub - apple/swift: The Swift Programming Language ideally with reproduction steps and a project if possible then I can make sure it gets routed to the right person to look at

1 Like

@jsam if you have created issue on Github, can you please share a link, so I can follow a topic. I have same situation
When I run swift app under the clean Vapor, it is all good, but soon as it is run under the docker env... it is complaining for:
Foundation/NSSwiftRuntime.swift:409: Fatal error: You must link or load module FoundationNetworking to load non-file: URL content using String(contentsOf:…), Data(contentsOf:…), etc.

Also, if you found a solution to this issue, share it : ))

Thank you

Update on my case:
Foundation/NSSwiftRuntime.swift:409: Fatal error: You must link or load module FoundationNetworking to load non-file: URL content using String(contentsOf:…), Data(contentsOf:…), etc.

This error was due to the wrong installation of Swift on Ubuntu ... now it is all good... up and working.

Could you provide more details on this please? Did you use a different image for Swift on Ubuntu or resolved the issue in some other way?

Hi @Max_Desiatov

Sorry for not being precise and clear,

Wrong installation of Swift on Ubuntu

I messed here a lot regarding the Swift installation on Ubuntu, I did something wrong, not sure what I've messed, but after reinstallation all is good
Current setup for Swift version / Ubuntu on my server playground:

  • Swift version 5.6.3 (swift-5.6.3-RELEASE), Target: x86_64-unknown-linux-gnu
  • Linux Mint 20.3 Cinnamon version 5.2.7

Hope situation is clearer and hope it helps
Cheers
:beers:

Is this issue reproducible for you with official Swift Docker images though, as reported by the OP?

My answer would be yes, because I was modifying Dockerfile

# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      libxml2 \

I can setup again tmrw again and let you know if you need this info?

This helped me a lot:

Did you use --static-swift-stdlib flag when building, as specified by the OP? In my testing with swift:5.6-focal image everything works fine with (default) dynamic linking, but produces the same NSSwiftRuntime.swift error when --static-swift-stdlib is passed to swift build. libcurl4 is installed, but that doesn't fix the error.

Yes, in Dockerfile:

swift build -c release --static-swift-stdlib

And for testing, I did a setup with supervisorctl, and my .sh for this is quite simple setup:

swift build --configuration release

Share what you have found / discovered : )
Really interested
Tnx

Yo, update from my tests, I gave you wrong informations, I had duplicated processes on server,
Not working with:

swift build -c release --static-swift-stdlib

Hi @Max_Desiatov

I've finally found some time, to come back to this topic ....

I still have issue with (default) dynamic linking ....

Do you have any updates?

and btw, I hope you are all good and fine :)

Thanks

Would you be able to provide the exact error message that you're seeing and self-contained reproduction code? Thanks!

Hi Max

When I run docker compose build, entire image is built fine, and all good there, also libxml2 package is installed as well (I've checked with docker exec to verify that I have package installed)

When I run docker compose up app, entire vapor app is started, and when I need to use FoundationNetworking or FoundationXML I have err:

Foundation/NSSwiftRuntime.swift:409: Fatal error: You must link or load module FoundationNetworking to load non-file: URL content using String(contentsOf:…), Data(contentsOf:…), etc.

On stackoverflow I've found that I need to modify import statements like this:

**import** Foundation

#if canImport(FoundationNetworking)

**import** FoundationNetworking

#endif

#if canImport(FoundationXML)

**import** FoundationXML

#endif

**import** Vapor

**import** SWXMLHash

**import** PostgresNIO

**import** FluentKit

Later I will share self-contained reproduction code

thanks in advance for any hint or help :beers:

Please also provide the exact command you use to build, it's important to understand whether you're using static linking or not.

1 Like

Sure, sharing dockerfile and docker-compose (and sorry MD is messing text formatting):

Dockerfile

# ================================
# Build image
# ================================
FROM swift:5.8-jammy as build

# Install OS updates and, if needed, sqlite3
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y\
    && rm -rf /var/lib/apt/lists/*

# Set up a build area
WORKDIR /build

# First just resolve dependencies.
# This creates a cached layer that can be reused
# as long as your Package.swift/Package.resolved
# files do not change.
COPY ./Package.* ./
RUN swift package resolve

# Copy entire repo into container
COPY . .

# Build everything, with optimizations
RUN swift build -c release --static-swift-stdlib

# Switch to the staging area
WORKDIR /staging

# Copy main executable to staging area
RUN cp "$(swift build --package-path /build -c release --show-bin-path)/Run" ./

# Copy resources bundled by SPM to staging area
RUN find -L "$(swift build --package-path /build -c release --show-bin-path)/" -regex '.*\.resources$' -exec cp -Ra {} ./ \;

# Copy any resources from the public directory and views directory if the directories exist
# Ensure that by default, neither the directory nor any of its contents are writable.
RUN [ -d /build/Public ] && { mv /build/Public ./Public && chmod -R a-w ./Public; } || true
RUN [ -d /build/Resources ] && { mv /build/Resources ./Resources && chmod -R a-w ./Resources; } || true

# ================================
# Run image
# ================================
FROM ubuntu:jammy

# Make sure all system packages are up to date, and install only essential packages.
RUN export DEBIAN_FRONTEND=noninteractive DEBCONF_NONINTERACTIVE_SEEN=true \
    && apt-get -q update \
    && apt-get -q dist-upgrade -y \
    && apt-get -q install -y \
      ca-certificates \
      tzdata \
# If your app or its dependencies import FoundationNetworking, also install `libcurl4`.
      libcurl4 \
# If your app or its dependencies import FoundationXML, also install `libxml2`.
      libxml2 \
      sudo \
    && rm -r /var/lib/apt/lists/*

# Create a vapor user and group with /app as its home directory
RUN useradd --user-group --create-home --system --skel /dev/null --home-dir /app vapor

# Switch to the new home directory
WORKDIR /app

# Copy built executable and any staged resources from builder
COPY --from=build --chown=vapor:vapor /staging /app

# Ensure all further commands run as the vapor user
USER vapor:vapor

# Let Docker bind to port 8080
EXPOSE 8080

# Start the Vapor service when the image is run, default to listening on 8080 in production environment
ENTRYPOINT ["./Run"]
CMD ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8080"]

Docker-compose

# Docker Compose file for Vapor
#
# Install Docker on your system to run and test
# your Vapor app in a production-like environment.
#
# Note: This file is intended for testing and does not
# implement best practices for a production deployment.
#
# Learn more: https://docs.docker.com/compose/reference/
#
#   Build images: docker-compose build
#      Start app: docker-compose up app
# Start database: docker-compose up db
# Run migrations: docker-compose run migrate
#       Stop all: docker-compose down (add -v to wipe db)
#
version: '3.7'

volumes:
  db_data:

x-shared_environment: &shared_environment
  LOG_LEVEL: ${LOG_LEVEL:-debug}
  DATABASE_HOST: db
  DATABASE_NAME: vapor_database
  DATABASE_USERNAME: vapor_username
  DATABASE_PASSWORD: vapor_password
  
services:
  app:
    image: river-level:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    depends_on:
      - db
    ports:
      - '8000:8000'
    # user: '0' # uncomment to run as root for testing purposes even though Dockerfile defines 'vapor' user.
    command: ["serve", "--env", "production", "--hostname", "0.0.0.0", "--port", "8000"]
  migrate:
    image: river-level:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    depends_on:
      - db
    command: ["migrate", "--yes"]
    deploy:
      replicas: 0
  revert:
    image: river-level:latest
    build:
      context: .
    environment:
      <<: *shared_environment
    depends_on:
      - db
    command: ["migrate", "--revert", "--yes"]
    deploy:
      replicas: 0
  db:
    image: postgres:14-alpine
    volumes:
      - db_data:/var/lib/postgresql/data/pgdata
    environment:
      PGDATA: /var/lib/postgresql/data/pgdata
      POSTGRES_USER: vapor_username
      POSTGRES_PASSWORD: vapor_password
      POSTGRES_DB: vapor_database
    ports:
      - '5432:5432'

As you're building with --static-swift-stdlib I think what you're seeing is `Data(contentsOf:)` crash due to missing `FoundationNetworking` when using static linking · Issue #4644 · apple/swift-corelibs-foundation · GitHub. Please follow the workaround I shared on that GitHub issue: you have to have at least a single use of a symbol (e.g. URLSession) from FoundationNetworking if you import it.

1 Like