Moving from python to swift using docker

I'm running several Python programs (that feed a db) using Docker, running on Ubuntu containers. As these programs have little to no dependencies, I would like to port these to Swift programs (speed gains as a first win).

Now, I've had a paid Apple Developer account for years, but had stopped paying, so now just a normal Developer account.

Questions:

  • Can I use Xcode to develop my Swift programs? Also without paying again?
  • What target should I use in Xcode? I'm not developing for Mac?
  • Any hints on the simplest skeleton I could use to start building from?

Yes you can use Xcode to write Swift programs. Create a SwiftPM app that works on macOS and as long as you stick to (most parts of) Foundation, it will run on Linux as well. You can also use VSCode with the Swift extension.

If you want to build a CLI then swift package init --type executable is the simplest thing to start with

3 Likes

Is there a place for me to look to for a "starter" Dockerfile that I can use for my Swift app, such that Github Actions can publish an image to DockerHub for me to run?

There are a number of ways to construct the Dockerfile, but Vapor's template has a fairly battle tested 'production ready' Dockerfile

1 Like

Thank you! And wow, that's a complicated Dockerfile (comparing to something similar for Python).

Well I mean you can just do

FROM swift
COPY .
swift run

But it depends on how build a final container you want, if you have any dependencies, how secure you want it etc. The Vapor Dockerfile is designed for running highly efficient, secure web severs that can handle real prod environments. So it may be overkill for what you're trying to do.

(One big benefit of the Vapor approach from a security point of view is that the final image does not contain a compiler or the Swift runtime outside of the static binary, so even if you managed to get remote code into the image, there's no way to compile or execute it since it's physically not possible)

5 Likes

I'm specifically not trying to run a web server, but want to run swift code in a container on a VM. It's basically a large While True loop that fetches new data regularly, and processes this to be stored in a MongoDB, so that's probably my only dependency.

Conceptually there's no difference - a web server is just a big while true loop that handles incoming requests

1 Like

So, @0xTim is technically correct and is probably responding to the characterization of the Vapor boilerplate as surprisingly complicated. Please allow me to suggest, however, that it's not actually complicated:

The Vapor Dockerfile really just says, "Look, here's what you need to have in order to compile and link this project, and then, here's what you need in order to run the project," as two separate targets. Presumably, one could run the thing on the same system that built the thing, but as a general rule one doesn't do development on a production machine, right?

In reading Docker files, the rule of thumb is sort of the opposite of YouTube, right? Read the comments first!

1 Like

Further to this, I'm still hung up on the web server side of things, ie I don't need a HTTP server listening for requests. I saw that Hummingbird does have Jobs, which seem suited for my purpose. To that end, can you also create a Hummingbird app with zero routes, but with Jobs?

Yes both Vapor and Hummingbird support running as a 'plain app' to do stuff without listening for requests

1 Like

based on your description, it feels like this is something you could do with a MongoDB driver and something like async-http-client.

The advantage of compiled language like Swift over interpreted language Python is that you don't need a runtime. So you can compile or cross-compile a single binary file that just runs without you needing to do anything.

Ok first try, trying this tutorial from swift.org.

this step:

$ swift run
Building for debugging...
...
Build complete! (59.87s)

Gives an error:

Build complete! (1.46s)
/Users/.../swift/HelloVapor/.build/checkouts/vapor/Sources/Vapor/HTTP/Server/HTTPServer.swift:350: Precondition failed: BUG DETECTED: wait() must not be called when on an EventLoop.
Calling wait() on any EventLoop can lead to
- deadlocks
- stalling processing of other connections (Channels) that are handled on the EventLoop that wait was called on

Further information:
- current eventLoop: Optional(SelectableEventLoop { selector = Selector { descriptor = 3 }, thread = NIOThread(name = NIO-SGLTN-0-#0), scheduledTasks = PriorityQueue(count: 0): [] })
- event loop associated to future: SelectableEventLoop { selector = Selector { descriptor = 5 }, thread = NIOThread(name = NIO-SGLTN-0-#2) }
[1]    30745 trace trap  swift run

Swift version:

❯ swift --version
Apple Swift version 5.9.2 (swiftlang-5.9.2.2.56 clang-1500.1.0.2.5)
Target: arm64-apple-darwin23.4.0

I started this tutorial with brew install vapor.

❯ brew info vapor
Warning: Treating vapor as a formula. For the cask, use homebrew/cask/vapor or specify the `--cask` flag.
==> vapor: stable 18.7.5 (bottled), HEAD
Command-line tool for Vapor (Server-side Swift web framework)
https://vapor.codes
Installed
/opt/homebrew/Cellar/vapor/18.7.5 (6 files, 7.3MB) *
  Poured from bottle using the formulae.brew.sh API on 2024-06-15 at 21:39:42
From: https://github.com/Homebrew/homebrew-core/blob/HEAD/Formula/v/vapor.rb
License: MIT
==> Requirements
Required: Xcode >= 13.3 (on macOS) ✔
==> Options
--HEAD
	Install HEAD version
==> Analytics
install: 490 (30 days), 1,499 (90 days), 6,630 (365 days)
install-on-request: 490 (30 days), 1,500 (90 days), 6,628 (365 days)
build-error: 0 (30 days)

Googled for the error, but to no avail. Failing at the simplest tutorial online is not very comforting.

Any hints where to look?

CC @graskind & @0xTim

What version of Vapor do you have in your Package.resolved and what version of Swift are you running?

You should not be hitting that error at all. Does running swift package update fix it? If not, can you show your entrypoint.swift code?

1 Like
{
  "pins" : [
    {
      "identity" : "async-http-client",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/swift-server/async-http-client.git",
      "state" : {
        "revision" : "a22083713ee90808d527d0baa290c2fb13ca3096",
        "version" : "1.21.1"
      }
    },
    {
      "identity" : "async-kit",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/vapor/async-kit.git",
      "state" : {
        "revision" : "7ece208cd401687641c88367a00e3ea2b04311f1",
        "version" : "1.19.0"
      }
    },
    {
      "identity" : "console-kit",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/vapor/console-kit.git",
      "state" : {
        "revision" : "9f7932f22ab6f64aafadc14491e694179b7d0f6f",
        "version" : "4.14.3"
      }
    },
    {
      "identity" : "multipart-kit",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/vapor/multipart-kit.git",
      "state" : {
        "revision" : "a31236f24bfd2ea2f520a74575881f6731d7ae68",
        "version" : "4.7.0"
      }
    },
    {
      "identity" : "routing-kit",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/vapor/routing-kit.git",
      "state" : {
        "revision" : "8c9a227476555c55837e569be71944e02a056b72",
        "version" : "4.9.1"
      }
    },
    {
      "identity" : "swift-algorithms",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-algorithms.git",
      "state" : {
        "revision" : "f6919dfc309e7f1b56224378b11e28bab5bccc42",
        "version" : "1.2.0"
      }
    },
    {
      "identity" : "swift-atomics",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-atomics.git",
      "state" : {
        "revision" : "cd142fd2f64be2100422d658e7411e39489da985",
        "version" : "1.2.0"
      }
    },
    {
      "identity" : "swift-collections",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-collections.git",
      "state" : {
        "revision" : "ee97538f5b81ae89698fd95938896dec5217b148",
        "version" : "1.1.1"
      }
    },
    {
      "identity" : "swift-crypto",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-crypto.git",
      "state" : {
        "revision" : "bc1c29221f6dfeb0ebbfbc98eb95cd3d4967868e",
        "version" : "3.4.0"
      }
    },
    {
      "identity" : "swift-http-types",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-http-types",
      "state" : {
        "revision" : "9bee2fdb79cc740081abd8ebd80738063d632286",
        "version" : "1.1.0"
      }
    },
    {
      "identity" : "swift-log",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-log.git",
      "state" : {
        "revision" : "e97a6fcb1ab07462881ac165fdbb37f067e205d5",
        "version" : "1.5.4"
      }
    },
    {
      "identity" : "swift-metrics",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-metrics.git",
      "state" : {
        "revision" : "ce594e71e92a1610015017f83f402894df540e51",
        "version" : "2.4.4"
      }
    },
    {
      "identity" : "swift-nio",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio.git",
      "state" : {
        "revision" : "9428f62793696d9a0cc1f26a63f63bb31da0516d",
        "version" : "2.66.0"
      }
    },
    {
      "identity" : "swift-nio-extras",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio-extras.git",
      "state" : {
        "revision" : "a3b640d7dc567225db7c94386a6e71aded1bfa63",
        "version" : "1.22.0"
      }
    },
    {
      "identity" : "swift-nio-http2",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio-http2.git",
      "state" : {
        "revision" : "8d8eb609929aee75336a0a3d2417280786265868",
        "version" : "1.32.0"
      }
    },
    {
      "identity" : "swift-nio-ssl",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio-ssl.git",
      "state" : {
        "revision" : "2b09805797f21c380f7dc9bedaab3157c5508efb",
        "version" : "2.27.0"
      }
    },
    {
      "identity" : "swift-nio-transport-services",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-nio-transport-services.git",
      "state" : {
        "revision" : "38ac8221dd20674682148d6451367f89c2652980",
        "version" : "1.21.0"
      }
    },
    {
      "identity" : "swift-numerics",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-numerics.git",
      "state" : {
        "revision" : "0a5bc04095a675662cf24757cc0640aa2204253b",
        "version" : "1.0.2"
      }
    },
    {
      "identity" : "swift-system",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/apple/swift-system.git",
      "state" : {
        "revision" : "f9266c85189c2751589a50ea5aec72799797e471",
        "version" : "1.3.0"
      }
    },
    {
      "identity" : "vapor",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/vapor/vapor.git",
      "state" : {
        "revision" : "f1c3495b5c35ab67881412acf20144112729b560",
        "version" : "4.101.3"
      }
    },
    {
      "identity" : "websocket-kit",
      "kind" : "remoteSourceControl",
      "location" : "https://github.com/vapor/websocket-kit.git",
      "state" : {
        "revision" : "4232d34efa49f633ba61afde365d3896fc7f8740",
        "version" : "2.15.0"
      }
    }
  ],
  "version" : 2
}

Remember I pasted the swift version in my previous question, it said 5.9.2.

Tried it again right now:

❯ swift --version
Apple Swift version 5.10 (swiftlang-5.10.0.13 clang-1500.3.9.4)
Target: arm64-apple-darwin23.5.0

:grimacing:

It compiles and runs, jay!
I'm pretty sure between posting the question and now checking, I updated MacOS to Sonoma 14.5, can that be the solution here?

No that shouldn't have caused any issues or fixes :thinking:

That error is normally caused by running some packages that haven't been updated properly for concurrency which is now enforced in the template (but easy to turn off)

Ok, well anyway I'm happy that I now have a path forward. Thanks!

1 Like

Happy to report I have a mini app running dockerized in a container on a Linux VM, including support for a MongoDB connection.

Now all that's left is to deep dive into concurrency and Swift 6 :slight_smile:

3 Likes