Estimated reading time: 15 minutes

Once you are ready with your application, you will probably want to put it somewhere.

In this page you will learn how to deploy your application to several providers and containers.

Table of contents:


When deploying, you will normally want to generate a single archive with all your classes, dependencies and resources packed together: either in a single JAR archive (also called Fat JAR) or a WAR file (Web Application Resource).

Fat JAR (Standalone)

A fat-jar (or uber-jar) archive allows you to generate a single archive to run your stand-alone embedded application directly using java: java -jar yourapplication.jar.

This is the preferred way for running in a container like docker, when deploying to heroku or when being reverse-proxied with nginx.


When using Gradle, you can use the shadow gradle plugin to generate it. For example, to generate a fat JAR using netty as engine:

buildscript {
    repositories {
    dependencies {
        classpath 'com.github.jengelman.gradle.plugins:shadow:2.0.2'

apply plugin: 'com.github.johnrengelman.plugin-shadow'
apply plugin: 'kotlin'
apply plugin: 'application'

mainClassName = 'io.ktor.server.netty.DevelopmentEngine'


When using Maven, you can generate a fat JAR archive with the maven-assembly-plugin. For example, to generate a fat JAR using netty as engine:


WAR (Servlet Container)

A WAR archive allows you to easily deploy your application inside your web container / servlet container, by just copying it to its webapps folder. Ktor supports two popular servlet container: Jetty and Tomcat. Also serves when deploying to google app engine.

To generate a war file, you can use the greety gradle plugin. You also need a WEB-INF/web.xml that looks like this:


<?xml version="1.0" encoding="ISO-8859-1" ?>

<web-app xmlns=""

        <!-- path to application.conf file, required -->

        <!-- required! -->

        <!-- 100mb max file upload, optional -->




buildscript {
    ext.gretty_version = '2.0.0'

    repositories {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "org.akhikhl.gretty:gretty:$gretty_version"

apply plugin: 'kotlin'
apply plugin: 'war'
apply plugin: 'org.akhikhl.gretty'

webAppDirName = 'webapp'

gretty {
    contextPath = '/'
    logbackConfigFile = 'resources/logback.xml'

sourceSets {
    main.kotlin.srcDirs = [ 'src' ]
    main.resources.srcDirs = [ 'resources' ]

repositories {
    maven { url "" }

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile "io.ktor:ktor-server-servlet:$ktor_version"
    compile "io.ktor:ktor-html-builder:$ktor_version"
    compile "ch.qos.logback:logback-classic:$logback_version"

kotlin.experimental.coroutines = 'enable'

task run

afterEvaluate {

For a full example:


If you have some restrictions on your JAR size (for example when deploying a free application to heroku), you can use proguard to shrink it. If you are using gradle, it is pretty straight forward using the proguard-gradle plugin. You only have to remember to keep: your main module method, the DevelopmentEngine class and the kotlin reflect classes. You can fine-tune it as required:

buildscript {
    ext.proguard_version = '6.0.1'
    dependencies {
        classpath "net.sf.proguard:proguard-gradle:$proguard_version"

task minimizedJar(type: proguard.gradle.ProGuardTask, dependsOn: shadowJar) {
    injars "build/libs/my-application.jar"
    outjars "build/libs/my-application.min.jar"
    libraryjars'java.home' + "/lib/rt.jar"
    printmapping "build/libs/"

    def keepClasses = [
            'io.ktor.server.netty.DevelopmentEngine', // The DevelopmentEngine you use, netty in this case.
            'io.ktor.samples.hello.HelloApplicationKt', // The class containing your module defined in the application.conf

    for (keepClass in keepClasses) {
        keep access: 'public', name: keepClass, {
            method access: 'public'
            method access: 'private'

You have a full example on:



Docker is a container engine: it allows you to pack and run applications, in a sandboxed layered lightweight environment, with its own isolated filesystem, operating system and resources.

You usually have to create a Dockerfile for monolithic services, and a docker-compose.yml when your container need to interact with other services, like for example a database or a redis.

First you have to create a fat-jar file with your application. And a Dockerfile, that would look like this:

FROM openjdk:8-jre-alpine
COPY ./build/libs/my-application.jar /root/my-application.jar
CMD ["java", "-server", "-Xms4g", "-Xmx4g", "-XX:+UseG1GC", "-XX:MaxGCPauseMillis=100", "-XX:+UseStringDeduplication", "-jar", "my-application.jar"]

For simple deploying to docker you can check the docker quickstart page for full details.


When using Docker with multiple domains, you might want to use the nginx-proxy image and the letsencrypt-nginx-proxy-companion image to serve multiple domains/subdomains in a single machine/ip and to automatically provide https, using let’s encrypt.

After configuring nginx-proxy and letsencrypt-nginx-proxy-companion, your docker-compose.yml file (without additional services) could look like this:

version: '2'
      context: ./
      dockerfile: Dockerfile
      - 8080
      - VIRTUAL_PORT=8080
      - reverse-proxy
    restart: always

      name: reverse-proxy

You can start it with docker-compose up -d and it will be restarted if the service fails or after a system reboot.

If the DNS for the specified domain is pointing to your server and you have configured correctly the nginx-proxy and its companion, the letsencrypt companion will contact with letsencrypt and will grab and configure the certificate automatically for you. So you will be able to access your http-only service via: nginx will handle the ssl certificates and will contact your server via plain http.


You have to generate a war file and put it in the webapps folder of tomcat.

For a complete example, check:


You have to generate a war file and put it in the webapps folder of jetty.

For a complete example, check:



There is a quickstart repository for heroku:


For using Heroku you will need Java, Maven/Gradle and the Heroku CLI

You will also need to configure your public key in the haroku configuration.

You can try the heroku --version command to see if you have the command line installed:

> heroku --version
heroku-cli/6.15.36 (darwin-x64) node-v9.9.0

You will also need an app.json file describing your projects and your dependencies:

  "name": "Start on Heroku: Kotlin",
  "description": "A barebones Kotlin app, which can easily be deployed to Heroku.",
  "image": "heroku/java",
  "addons": [ "heroku-postgresql" ]

You will also need a Procfile describing what to execute:

web:    java -jar target/helloworld.jar

And a file describing your java version:


Running locally

And a file called .env along the other files with (just required for development). This will contain environment variables that heroku will pass to the application. For example, for the quickstart:


If your local installation of postgresql has user/password, you have to change the jdbc uri too:


You will also need to create that database first:

> psql -c "CREATE DATABASE java_database_name;"


With those files, you can use gradle or maven to create a far-jar and adjust the Procfile pointing to the right file.

After building the jar, in Unix systems you can use heroku local:start to start your server.


You first have to create an app or set the git remote. heroku create will create an app with a random available name and will set a git remote of the repo. After calling heroku create you should see something like this:

> heroku create
Creating app... done, ⬢ demo-demo-12345 |

This effectively adds a heroku remote to your git clone:

> cat .git/config
[remote "heroku"]
	url =
	fetch = +refs/heads/*:refs/remotes/heroku/*

After that you have to push your git changes to the heroku remote. And it does a build on push:

> git push heroku master
Counting objects: 90, done.
Delta compression using up to 4 threads.
Compressing objects: 100% (59/59), done.
Writing objects: 100% (90/90), 183.08 KiB | 5.55 MiB/s, done.
Total 90 (delta 21), reused 0 (delta 0)
remote: Compressing source files... done.
remote: Building source:
remote: -----> Java app detected
remote: -----> Installing JDK 1.8... done
remote: -----> Executing: ./mvnw -DskipTests clean dependency:list install
remote:        [INFO] BUILD SUCCESS
remote:        [INFO] ------------------------------------------------------------------------
remote:        [INFO] Total time: 49.698 s
remote:        [INFO] Finished at: 2018-03-23T04:33:01+00:00
remote:        [INFO] Final Memory: 41M/399M
remote:        [INFO] ------------------------------------------------------------------------
remote: -----> Discovering process types
remote:        Procfile declares types -> web
remote: -----> Compressing...
remote:        Done: 60.7M
remote: -----> Launching...
remote:        Released v4
remote: deployed to Heroku
remote: Verifying deploy... done.
 * [new branch]      master -> master

Now you can execute heroku open to open your application in your browser:

heroku open

In this case it would open:

Google App Engine

You can check a full google appengine sample, here:

Google App Engine use servlets.


You first need to install the gcloud cli. You can grab it from here: and follow the described steps to install it.

For example, a macOS setup could look like this:

> wget
> tar -xzf google-cloud-sdk-194.0.0-darwin-x86_64.tar.gz
> cd google-cloud-sdk

> ./
Welcome to the Google Cloud SDK!

To help improve the quality of this product, we collect anonymized usage data
and anonymized stacktraces when crashes are encountered; additional information
is available at <>. You may choose
to opt out of this collection now (by choosing 'N' at the below prompt), or at
any time in the future by running the following command:

    gcloud config set disable_usage_reporting true

Do you want to help improve the Google Cloud SDK (Y/n)?  n

Your current Cloud SDK version is: 194.0.0
The latest available version is: 194.0.0

│                                                  Components                                                 │
│     Status    │                         Name                         │            ID            │    Size   │
│ Not Installed │ App Engine Go Extensions                             │ app-engine-go            │ 151.3 MiB │
│ Not Installed │ Cloud Bigtable Command Line Tool                     │ cbt                      │   4.0 MiB │
│ Not Installed │ Cloud Bigtable Emulator                              │ bigtable                 │   3.8 MiB │
│ Not Installed │ Cloud Datalab Command Line Tool                      │ datalab                  │   < 1 MiB │
│ Not Installed │ Cloud Datastore Emulator                             │ cloud-datastore-emulator │  17.9 MiB │
│ Not Installed │ Cloud Datastore Emulator (Legacy)                    │ gcd-emulator             │  38.1 MiB │
│ Not Installed │ Cloud Pub/Sub Emulator                               │ pubsub-emulator          │  33.4 MiB │
│ Not Installed │ Emulator Reverse Proxy                               │ emulator-reverse-proxy   │  14.5 MiB │
│ Not Installed │ Google Container Local Builder                       │ container-builder-local  │   3.7 MiB │
│ Not Installed │ Google Container Registry's Docker credential helper │ docker-credential-gcr    │   2.5 MiB │
│ Not Installed │ gcloud Alpha Commands                                │ alpha                    │   < 1 MiB │
│ Not Installed │ gcloud Beta Commands                                 │ beta                     │   < 1 MiB │
│ Not Installed │ gcloud app Java Extensions                           │ app-engine-java          │ 118.9 MiB │
│ Not Installed │ gcloud app PHP Extensions                            │ app-engine-php           │  21.9 MiB │
│ Not Installed │ gcloud app Python Extensions                         │ app-engine-python        │   6.2 MiB │
│ Not Installed │ gcloud app Python Extensions (Extra Libraries)       │ app-engine-python-extras │  27.8 MiB │
│ Not Installed │ kubectl                                              │ kubectl                  │  12.2 MiB │
│ Installed     │ BigQuery Command Line Tool                           │ bq                       │   < 1 MiB │
│ Installed     │ Cloud SDK Core Libraries                             │ core                     │   7.4 MiB │
│ Installed     │ Cloud Storage Command Line Tool                      │ gsutil                   │   3.4 MiB │
To install or remove components at your current SDK version [194.0.0], run:
  $ gcloud components install COMPONENT_ID
  $ gcloud components remove COMPONENT_ID

To update your SDK installation to the latest version [194.0.0], run:
  $ gcloud components update

Modify profile to update your $PATH and enable shell command

Do you want to continue (Y/n)?  Y

The Google Cloud SDK installer will now prompt you to update an rc
file to bring the Google Cloud CLIs into your environment.

Enter a path to an rc file to update, or leave blank to use
Backing up [/Users/user/.zshrc] to [/Users/user/.zshrc.backup].
[/Users/user/.zshrc] has been updated.

==> Start a new shell for the changes to take effect.

For more information on how to get started, please visit:

After that you can start a new shell, and you should have access to the gcloud cli. For example:

> gcloud --version
Google Cloud SDK 194.0.0
bq 2.0.30
core 2018.03.16
gsutil 4.29

You will also need to install some components with the cli (gcloud components install app-engine-java):

> gcloud components install app-engine-java

Your current Cloud SDK version is: 194.0.0
Installing components from version: 194.0.0

│        These components will be installed.         │
│             Name             │ Version │    Size   │
│ gRPC python library          │         │           │
│ gRPC python library          │   1.9.1 │   7.6 MiB │
│ gcloud app Java Extensions   │  1.9.63 │ 118.9 MiB │
│ gcloud app Python Extensions │  1.9.67 │   6.2 MiB │

For the latest full release notes, please visit:

Do you want to continue (Y/n)?  Y

╠═ Creating update staging area                             ═╣
╠═ Installing: gRPC python library                          ═╣
╠═ Installing: gRPC python library                          ═╣
╠═ Installing: gcloud app Java Extensions                   ═╣
╠═ Installing: gcloud app Python Extensions                 ═╣
╠═ Creating backup and activating new installation          ═╣

Performing post processing steps...done.

Update done!

For your project, you can use gradle and the official appengine-gradle-plugin. So a build.gradle would look like this:

buildscript {
    ext.appengine_version = '1.9.60'
    ext.appengine_plugin_version = '1.3.4'

    repositories {
    dependencies {
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath "$appengine_plugin_version"

apply plugin: 'kotlin'
apply plugin: 'war'
apply plugin: ''

// appengine does not honor this property, so we are forced to use deep Maven tree layout
// webAppDirName = file('webapp')

sourceSets {
    main.kotlin.srcDirs = [ 'src/main/kotlin' ]

repositories {
    maven { url "" }

dependencies {
    compile "org.jetbrains.kotlin:kotlin-stdlib-jdk8:$kotlin_version"
    compile "io.ktor:ktor-server-servlet:$ktor_version"
    compile "io.ktor:ktor-html-builder:$ktor_version"
    compile "org.slf4j:slf4j-jdk14:$slf4j_version"

    providedCompile "$appengine_version"

kotlin.experimental.coroutines = 'enable'

task run(dependsOn: appengineRun)

Once everything is configured, you can now run the application locally. Using the gradle task appengineRun:

In this case I’m executing this command in the root of the ktor-samples repository

./gradlew :google-appengine-standard:appengineRun

It should start the server in http://localhost:8080/ and the admin in http://localhost:8080/_ah/admin.


First we need to create a project gcloud projects create demo-demo-123456 --set-as-default:

> gcloud projects create demo-demo-123456 --set-as-default
Create in progress for [].
Waiting for [operations/pc.7618150612308930095] to finish...done.
Updated property [core/project] to [demo-demo-123456].

And then we need to create an application using gcloud app create:

> gcloud app create
You are creating an app for project [demo-demo-123456].
WARNING: Creating an App Engine application for a project is irreversible and the region
cannot be changed. More information about regions is at

Please choose the region where you want your App Engine application

 [1] europe-west2  (supports standard and flexible)
 [2] us-central    (supports standard and flexible)
 [3] europe-west   (supports standard and flexible)
 [4] europe-west3  (supports standard and flexible)
 [5] us-east1      (supports standard and flexible)
 [6] us-east4      (supports standard and flexible)
 [7] asia-northeast1 (supports standard and flexible)
 [8] asia-south1   (supports standard and flexible)
 [9] australia-southeast1 (supports standard and flexible)
 [10] southamerica-east1 (supports standard and flexible)
 [11] northamerica-northeast1 (supports standard and flexible)
 [12] cancel
Please enter your numeric choice:  1

Creating App Engine application in project [demo-demo-123456] and region [europe-west2]....done.
Success! The app is now created. Please use `gcloud app deploy` to deploy your first app.

Now we can deploy the application using gradle appengineDeploy:

> gradle :google-appengine-standard:appengineDeploy
Starting a Gradle Daemon (subsequent builds will be faster)
Reading application configuration data...
Mar 23, 2018 6:32:09 AM readConfigXml
INFORMACIÓN: Successfully processed /Users/user/projects/ktor-samples/deployment/google-appengine-standard/build/exploded-google-appengine-standard/WEB-INF/appengine-generated/datastore-indexes-auto.xml

Beginning interaction for module default...
0% Scanning for jsp files.
0% Generated git repository information file.
Temporary staging for module default directory left in /Users/user/projects/ktor-samples/deployment/google-appengine-standard/build/staged-app
Services to deploy:

descriptor:      [/Users/user/projects/ktor-samples/deployment/google-appengine-standard/build/staged-app/app.yaml]
source:          [/Users/user/projects/ktor-samples/deployment/google-appengine-standard/build/staged-app]
target project:  [demo-demo-123456]
target service:  [default]
target version:  [20180323t063212]
target url:      []

Beginning deployment of service [default]...
Some files were skipped. Pass `--verbosity=info` to see which ones.
You may also view the gcloud log file, found at
#= Uploading 38 files to Google Cloud Storage               =#
File upload done.
Updating service [default]...
Setting traffic split for service [default]...
Deployed service [default] to []

You can stream logs from the command line by running:
  $ gcloud app logs tail -s default

To view your application in the web browser run:
  $ gcloud app browse

6 actionable tasks: 2 executed, 4 up-to-date

Now you can view your application in your browser with gcloud app browse. It will open the application. In this case:


In the Ktor’s samples repository, you can find examples and README files on how to deploy to specific providers.