|
| 1 | +# Packaging Applications for Deployment |
| 2 | + |
| 3 | +Once the application is built for production, it still needs to be packaged before it can be deployed to servers. There are several strategies for packaging Swift applications for deployment. |
| 4 | + |
| 5 | +## Docker |
| 6 | + |
| 7 | +One of the most popular ways to package applications these days is using container technologies such as [Docker](https://www.docker.com). |
| 8 | + |
| 9 | +Using Docker's tooling, we can build and package the application as a Docker image, publish it to a Docker repository, and later launch it directly on a server or on a platform that supports Docker deployments such as [Kuberenetes](https://kubernetes.io). Many public cloud provides including AWS, GCP, Azure, IBM and others encourage this kind of deployment. |
| 10 | + |
| 11 | +Here is an example `Dockerfile` that builds and packages the application on top of CentoOS: |
| 12 | + |
| 13 | +```Dockerfile |
| 14 | +#------- build ------- |
| 15 | +FROM swiftlang/swift:nightly-centos8 as builder |
| 16 | + |
| 17 | +# set up the workspace |
| 18 | +RUN mkdir /workspace |
| 19 | +WORKDIR /workspace |
| 20 | + |
| 21 | +# copy the source to the docker image |
| 22 | +COPY . /workspace |
| 23 | + |
| 24 | +RUN swift build -c release -Xswiftc -g |
| 25 | + |
| 26 | +#------- package ------- |
| 27 | +FROM centos:8 |
| 28 | +# copy executables |
| 29 | +COPY --from=builder /workspace/.build/release/<executable-name> / |
| 30 | +# copy swift's dynamic libraries dependencies |
| 31 | +COPY --from=builder /usr/lib/swift/linux/lib*so* / |
| 32 | + |
| 33 | +# set the entry point (application name) |
| 34 | +CMD ["<executable-name>"] |
| 35 | +``` |
| 36 | + |
| 37 | +To create the local Docker image from the `Dockerfile` use the `docker build` command from the application's source location, e.g.: |
| 38 | + |
| 39 | +```bash |
| 40 | +$ docker build . -t <my-app>:<my-app-version> |
| 41 | +``` |
| 42 | + |
| 43 | +To test the local image use the `docker run` command, .e.g.: |
| 44 | + |
| 45 | +```bash |
| 46 | +$ docker run <my-app>:<my-app-version> |
| 47 | +``` |
| 48 | + |
| 49 | +Finally, use the `docker push` command to publish the Application's Docker image to a Docker repository of your choice. e.g.: |
| 50 | + |
| 51 | +```bash |
| 52 | +$ docker tag <my-app>:<my-app-version> <docker-hub-user>/<my-app>:<my-app-version> |
| 53 | +$ docker push <docker-hub-user>/<my-app>:<my-app-version> |
| 54 | +``` |
| 55 | + |
| 56 | +At this point, the application's Docker image is ready to be deployed to the server hosts (which need to run docker), or to one of the platforms that support Docker deployments. |
| 57 | + |
| 58 | +See [Docker's documentation](https://docs.docker.com/engine/reference/commandline/) for more complete information about Docker. |
| 59 | + |
| 60 | +### Distroless |
| 61 | + |
| 62 | +[Distroless](https://github.com/GoogleContainerTools/distroless) is a project by Google that attempts to create minimal images that contain only your application and its runtime dependencies. They do not contain package managers, shells or any other programs you would expect to find in a standard Linux distribution. |
| 63 | + |
| 64 | +Since distroless supports Docker and is based on Debian, packaging a Swift application on it is fairly similar to the above. Here is an example `Dockerfile` that builds and packages the application on top of a distroless's base image: |
| 65 | + |
| 66 | +```Dockerfile |
| 67 | +#------- build ------- |
| 68 | +# using Ubuntu Bionic to build the Swift program |
| 69 | +# since its compatible with Debian rutime |
| 70 | +FROM swiftlang/swift:nightly-bionic as builder |
| 71 | + |
| 72 | +# set up the workspace |
| 73 | +RUN mkdir /workspace |
| 74 | +WORKDIR /workspace |
| 75 | + |
| 76 | +# copy the source to the docker image |
| 77 | +COPY . /workspace |
| 78 | + |
| 79 | +RUN swift build -c release -Xswiftc -g |
| 80 | + |
| 81 | +#------- package ------- |
| 82 | +FROM gcr.io/distroless/cc-debian10 |
| 83 | +# copy executables |
| 84 | +COPY --from=builder /workspace/.build/release/<executable-name> / |
| 85 | +# copy swift's dynamic libraries dependencies |
| 86 | +COPY --from=builder /usr/lib/swift/linux/lib*so* / |
| 87 | + |
| 88 | +# set the entry point (application name) |
| 89 | +CMD ["<executable-name>"] |
| 90 | +``` |
| 91 | + |
| 92 | +Note the above this uses `gcr.io/distroless/cc-debian10` as the runtime image which should work for Swift programs that do not use `FoundationNetworking` or `FoundationXML`. In order to provide a more complete support we (the community) could put in a PR into distroless to introduce a base image for Swift that includes `libcurl` and `libxml` which are required for `FoundationNetworking` and `FoundationXML` respectively. |
| 93 | + |
| 94 | +## Archive (Tarball, Zipfile, etc) |
| 95 | + |
| 96 | +Since cross compiling Swift for Linux is not [yet] supported on Mac or Windows, we need to use use virtualization technologies like Docker to compile applications we are targeting to run on Linux. |
| 97 | + |
| 98 | +That said, this does not mean we must also package the applications as Docker images in order to deploy them. While using Docker images for deployment is convenient and popular, an application can also be packaged using a simple and lightweight archive format like tarball or zipfile, then uploaded to the server where it can be extracted and run. |
| 99 | + |
| 100 | +Here is an example of using Docker and `tar` to build and package the application for deployment on Ubuntu servers: |
| 101 | + |
| 102 | +First, use the `docker run` command from the application's source location to build the application: |
| 103 | + |
| 104 | +```bash |
| 105 | +$ docker run --rm \ |
| 106 | + -v `pwd`:/workspace \ |
| 107 | + -w /workspace \ |
| 108 | + swift:5.2-bionic \ |
| 109 | + /bin/bash -cl "swift build -c release -Xswiftc -g" |
| 110 | +``` |
| 111 | + |
| 112 | +Note we are bind mounting the source directory so that the build writes the build artifacts to the local drive from which we will package them. |
| 113 | + |
| 114 | +Next we can create a staging area with the application's executables and Swift's dynamic libraries dependencies: |
| 115 | + |
| 116 | +```bash |
| 117 | +$ docker run --rm \ |
| 118 | + -v `pwd`:/workspace \ |
| 119 | + -w /workspace \ |
| 120 | + swift:5.2-bionic \ |
| 121 | + /bin/bash -cl ' \ |
| 122 | + rm -rf .build/install && mkdir -p .build/install; \ |
| 123 | + cp -P .build/release/<executable-name> .build/install/; \ |
| 124 | + cp -P /usr/lib/swift/linux/lib*so* .build/install/' |
| 125 | +``` |
| 126 | + |
| 127 | +Note this command could be combined with the build command above, we separated it to make the example more readable. |
| 128 | + |
| 129 | +Finally, create a tarball from the staging directory: |
| 130 | + |
| 131 | +```bash |
| 132 | +$ tar cvzf <my-app>-<my-app-version>.tar.gz -C .build/install . |
| 133 | +``` |
| 134 | + |
| 135 | +We can test the integrity of the tarball by extracting it to a directory and running the application in a Docker container: |
| 136 | + |
| 137 | +```bash |
| 138 | +$ cd <extracted directory> |
| 139 | +$ docker run -v `pwd`:/app -w /app swift:5.2-bionic-slim ./<executable-name> |
| 140 | +``` |
| 141 | + |
| 142 | +Deploying the application's tarball to the target server can be done using utilities like `scp`, or in a more sophisticated setup with configuration management system like `chef`, `puppet`, `ansible`, etc |
| 143 | + |
| 144 | + |
| 145 | +## Source Distribution |
| 146 | + |
| 147 | +Another distribution technique popular with dynamic languages like Ruby or Javascript is distributing the source to the server, then compiling it on the server itself. |
| 148 | + |
| 149 | +To build Swift applications directly on the server, the server must have the correct Swift toolchain installed. [Swift.org](https://swift.org/download/#linux) publishes toolchains for a variety of Linux distributions, make sure to use the one matching your server Linux version and desired Swift version. |
| 150 | + |
| 151 | +The main advantage of this approach is that it is easy. Additional advantage is the server has the full toolchain (e.g. debugger) that can help troubleshoot issues "live" on the server. |
| 152 | + |
| 153 | +The main disadvantage of this approach that the server has the full toolchain (e.g. compiler) which means a sophisticated attacker can potentially find ways to execute code. They can also potentially gain access to the source code which might be sensitive. If the application code needs to cloned from a private or protected repository, the server needs access to credentials which adds additional attack surface area. |
| 154 | + |
| 155 | +In most cases, source distribution is not advised due to these security concerns. |
0 commit comments