Although Swift and Vapor is not built to actually run on Debian, but for Ubuntu, let’s say that both distributions share enough so that running Vapor applications on it is not impossible.

Since it’s not actually a supported solution, please note that maybe some features or things might not work properly. But it worked for my implementation of Padlok API.

You’ll be able to learn more about my Padlok project and implementations in a later story.

About Vapor, and Swift server-side

Vapor is a Swift framework allowing a server-side application to be written in Swift, compiled and then run server side.

It comes with an HTTP implementation, and many other Foundation replacement methods for dealing with thread, files and other subtleties that Swift would not support outside of MacOS otherwise.

The compromise of making the back-end is Swift is, of course, re-usable code. Being able to share data modeling between iOS code & back-end code makes things like REST API easier to apprehend; even though it requires to actually learn other patterns for dealing with simple things.

The lack of Foundation is often problematic when you’re used to develop on Apple platforms. Any NS prefixed object become unavailable on Linux ; so we need to keep that in mind when designing an application destined for something else than a Mac server, which is my case here.

Choosing Vapor instead of PHP for Padlok

My first version of Padlok API was built using PHP. It was a “sweet spot” choice in my case: PHP was the first programming language I ever used almost two decades ago, and made me very easy for me to iterate on.

Plus, the requirement of the first version of the API was fairly simple:

  • No authentication
  • No user data
  • Just a couple endpoints for dealing with parameters I would change dynamically on the application like URLs, or specific parameters.

Serving a JSON file was fairly easy, and took only a few dozen lines of code. And it was fast to create by using a modern PHP framework intended to. For the record, I used Slim framework.

Therefore, my needs shifted. I’m actually rethinking the share feature of Padlok, and it requires way more work to make it happen, with a lot of requirements:

  • Full crypto implementation, for dealing with encrypted data. I want the server not to be aware of any of the data that is shared, making the share secure, and privacy focused
  • More robust modelization, shared with the iOS app, to be able to fetch & send data with the API.

I will come more in the share design and privacy consideration in its own story later, but the choice of sharing some encryption/decryption code along with the modelization between iOS and my back-end became clear. And with Swift Crypto being open-sourced by Apple, making the crypto code Swift only, secure, and compatible is just a piece of cake.

The true question that kept was making Swift & Vapor actually work on my VPS, that is currently running Debian 11 Bullseyes. And since I don’t want to switch to Ubuntu, I made it work on Debian.

Install Swift on Debian 11 Bullseyes

First, we’ll install the Swift dependencies, according to Vapor own deployment instructions

sudo apt-get install clang libicu-dev libatomic1 build-essential pkg-config

And also some Vapor requirements:

sudo apt-get install openssl libssl-dev zlib1g-dev libsqlite3-dev

Then, we download and decompress the latest release of Swift for the latest Ubuntu distribution (at the time of writing, Swift 5.5.2 and Ubuntu 20.04)

wget https://download.swift.org/swift-5.5.2-release/ubuntu2004/swift-5.5.2-RELEASE/swift-5.5.2-RELEASE-ubuntu20.04.tar.gz
tar xzf swift-5.5.2-RELEASE-ubuntu20.04.tar.gz && rm swift-5.5.2-RELEASE-ubuntu20.04.tar.gz

Finally, we move and link swift at a proper place. Let’s use /opt/ for keeping different version of swift properly, and only linking what we actually require:

sudo mkdir -p /opt/swift
sudo mv swift-5.5.2-RELEASE-ubuntu20.04/ /opt/swift/5.5.2
sudo ln -s /opt/swift/5.5.2/usr/bin/swift /usr/local/bin/swift

Swift should now be available on your Debian:

$ swift --version
Swift version 5.5.2 (swift-5.5.2-RELEASE)
Target: x86_64-unknown-linux-gnu

Please note the “unknown linux gnu” target; since we’re a bit out of scope for Swift here. But it’s okay!

Build & Run your Vapor app

Now that Swift is around and after cloning your project from git, we may build it!

cd /path/to/project
swift build -c release

And run on port 8080

$ .build/release/Run serve
[ NOTICE ] Server starting on http://127.0.0.1:8080

Later, we’ll require to make this process persistent, and therefore bind the localhost port 8080 to port 80/443 ; and in my case, it’ll happen with Nginx that also handle other websites, and SSL configuration.

But if you’re fine enough with port 80 for debug, you might wanna run the server on port 80 on your public IP ; using your public IP of course (here, for instance, we use 157.245.244.228)

$ sudo .build/release/Run serve -b 157.245.244.228:80

Persistence with supervisor and Nginx proxy configuration

Supervising Vapor with Supervisor

For persistence, and automatic starting of the service when the server is rebooted, we’re going to use Supervisor.

Supervisor is an efficient service that will keep track of pid that should stay around. If the program is missing, or crashed, the process will get restarted.

First, we’ll need to install supervisor

sudo apt-get install supervisor

For more convenience, we’ll create a “vapor” user, that will own the process. It’ll be the equivalent for www-data that is often used by Apache and PHP.

sudo adduser --system -no-create-home vapor

And now we can register the supervisor app by creating the file /etc/supervisor/conf.d/vapor.conf

[program:vapor]
command=/path/to/project/.build/release/Run serve --env production
directory=/path/to/project/
user=vapor
stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log
stderr_logfile=/var/log/supervisor/%(program_name)-stderr.log

And add the app to supervisor:

sudo supervisorctl reread
sudo supervisorctl add vapor

And now, even after a reboot, your app is available at 127.0.0.1:8080. We now just lack a small proxy with Nginx to make it available to the outside world.

To the edge of the outside world with Nginx

The final step is to bind localhost:8080 with port 443 using nginx. It’s possible to also bind port 80, but it’s less and less recommended to support unsecure connections anymore.

Then you’ll be able to set your server rule with the proper proxy redirection:

server {
    server_name my-vapor-app.com;
    listen 443;

    # We miss all the declarations to make tls happens
    # But they should be somewhere here

    root /path/to/project/Public/;

    # Serve all public/static files via nginx and then fallback to Vapor for the rest
    location / {
        try_files $uri @proxy;
    }

    # Proxy configuration for binding to Vapor port
    location @proxy {
        proxy_pass http://127.0.0.1:8080;
        proxy_pass_header Server;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_pass_header Server;
        proxy_connect_timeout 3s;
        proxy_read_timeout 10s;
    }
}

Final thoughts

Leaving my comfort zone from PHP to Vapor is now a pretty good experience. Of course, some things might not be as easy to do than with PHP, but the simplicity of the modelization, the free validation behaviors with Swift Codable protocol, and many other behaviors makes it very neat to write an API using Vapor.

I’m considering making the Padlok API open source at some point. I’ll update this story accordingly if/when that happens. Open sourcing would have benefits, for user trust in what data is actually being collected by the app, but also regarding the technics involved to protect their privacy and data.

In the meanwhile, you can learn more about my Padlok app on my marketing website and download it from the App Store.