cc by-sa flurdy

Pizza in the cloud

with Scala, Play and Docker Cloud

How to create a pizza ordering service application using Scala and the Play Framework. Wrap it in a Docker container and deploy it with Docker Cloud.

Started: November 2016. Last updated: 6th Dec 2016.

Deja-Vue

My pizza service analogy works well as an example as it covers showing pages, posting forms, and reading changed data. And I have over time written several howtos that use the pizza service analogy, the Play framework, and hosting with various cloud providers:

Build environment

Java and Scala

You do need Java JDK v8 installed. Scala however can be installed but the build tool below will download a Scala SDK instance so not neccessary. You can use OpenJDK or Oracle's own JDK version.

Oracle JDK

For macOS go to Oracle's JDK page, choose the latest JDK8 version, accept the license, download and run the dmg file.

For Ubuntu users install Oracle JDK8 via apt-get:

sudo add-apt-repository ppa:webupd8team/java;
sudo apt-get update;
sudo apt-get install oracle-java8-installer;
sudo apt-get install oracle-java8-set-default

OpenJDK

For macOS use Homebrew:

brew cask install java

On Ubuntu v14.04 & v12.04, you need to add a PPA, for v14.10 and later its in the standard repositories.

sudo add-apt-repository ppa:openjdk-r/ppa;
sudo apt-get update;

Then for all Ubuntu versions:

sudo apt-get install openjdk-8-jdk

Then also on Ubuntu run and choose JDK 8 for both of these:

sudo update-alternatives --config java sudo update-alternatives --config javac

Activator

Download and install Activator, Lightbend's (né Typesafe) extension of SBT, the near default Scala build tool. Activator includes a UI which you do not need, but does include the handy new command to scaffold Play and Akka applications.

Activator comes in two flavours: full and mini. Full includes common dependencies for Play and Akka. Full will save you a little time initially, but soon the dependencies versions will move along and you have to download them all like mini via SBT's use of Ivy anyway.

On macOS:

brew install typesafe-activator

On Linux download and extract the Zip:

wget https://downloads.typesafe.com/typesafe-activator/1.3.12/typesafe-activator-1.3.12-minimal.zip;
unzip typesafe-activator-1.3.12-minimal.zip;
sudo mv activator-1.3.12-minimal /opt/activator

then add /opt/activator/bin to your $PATH environment variable.

SBT

I recommend also installing SBT, as 99% of the time you don't need the extra cruft of Activator. There are other Scala build tools such as Maven and Gradle, but there is little reason to use those with Scala and again you will painfully swim against the current of 99% of projects and companies that defaults to SBT.

On macOS:

brew install sbt

Or on Ubuntu/debian install SBT via apt-get.

echo "deb https://dl.bintray.com/sbt/debian /" | \
 sudo tee -a /etc/apt/sources.list.d/sbt.list;
sudo apt-key adv --keyserver hkp://keyserver.ubuntu.com:80 \
 --recv 2EE0EA64E40A89B84B2DF73499E82A75642AC823;
sudo apt-get update;
sudo apt-get install sbt

Play scaffold

Since v2.4 Play has used dependency injection, which is the most significant difference in my pizza ordering application compared to my previous iterations. Play's choice of run time DI with Guice goes against my strong preference for compile time DI, but it is futile to swim against the current.

Scaffold

Create the new application:

activator new pizzeria

Choose option 6, the play-scala option.

You now have a basic play application folder structure inside pizzeria:

  • conf/build.sbt is where you define your libraries, versions and name of the application.
  • conf/routes is where you define the routing of request's url paths to the controllers.
  • app/controllers/HomeController is one example controller that currently shows the home page.
  • app/views/main.scala.html is the shared common template for most views.
  • app/views/index.scala.html is the example template for the home page content.
  • public/stylesheets/main.css is the default style sheet. Feel free to rename or add more.

Download the internet

Since we used the mini flavour of Activator we need to download all the dependencies.

Launch Activator or SBT (and make some tea):

sbt

Compile and test the code (drink the tea and fetch some biscuits)

;compile;test

Then run the application (and make some more tea)

run

Thankfully you will not need to download the internet from now on, as your local ~/.ivy2 folder is now enormous.

Since we haven't changed anything this should all work, and the application will now be available on the default port of 9000.

curl localhost:9000

Press ctrl+d to stop. Then exit SBT:

exit

You now have a working scaffold. Note a shortcut to run the app:

sbt run

Dockerise

Install Docker

On macOS you can install Docker via Brew, which I used to do. But the "new" Docker for Mac is a better choice. Download and run the dmg file. Launch the Docker app via Launchpad.

For Linux it depends on the distrobution, for Ubuntu 16.04:

sudo apt-get install apt-transport-https ca-certificates;
sudo apt-key adv \
 --keyserver hkp://ha.pool.sks-keyservers.net:80 \
 --recv-keys 58118E89F3A912897C070ADBF76221572C52609D;
echo "deb https://apt.dockerproject.org/repo ubuntu-xenial main" | \
 sudo tee /etc/apt/sources.list.d/docker.list;
sudo apt-get update;
sudo apt-get install linux-image-extra-$(uname -r) \
 linux-image-extra-virtual;
sudo apt-get install docker-engine;
sudo service docker start

Dockerfile

You have a few options of how you build your images. Mostly due to relatively slowness of downloading all Ivy dependencies from scratch on every build.

You can build the jar file outside of Docker. Then just add the jar or target/stage folder to the Dockerfile. This is result in very fast Docker image builds, but adds another step that is just as slow before it.

For that you either build manually in the project folder via sbt package or sbt stage (for Play apps).

Or via a build/continuous integration server that uploads it to a Maven/Ivy repository.

Or you build it inside Docker.

Either on every build, this is the least complicated method but also very, very slow. This is the method we will use in this document.

Or you depend on a base image with all the dependencies preload in ~/.ivy2. Similar to my play-framework-base. You then extend your Dockerfile from this base image. Note the versions of the dependencies in the base and your project must match for it to be of use.

Create a Dockerfile in the root folder. This will use my my play-framework image, so the default dependencies for Play is already part of the image but not any other dependencies.

FROM flurdy/play-framework:2.5.10-alpine

MAINTAINER Ivar Abrahamsen <@flurdy>

COPY conf /etc/app/

ADD . /opt/build/

WORKDIR /opt/build

RUN /opt/activator/bin/activator clean stage && \
  rm -f target/universal/stage/bin/*.bat && \
  mv target/universal/stage/bin/* target/universal/stage/bin/app && \
  mv target/universal /opt/app && \
  ln -s /opt/app/stage/logs /var/log/app && \
  rm -rf /opt/build && \
  rm -rf /root/.ivy2

WORKDIR /opt/app

ADD . /opt/build/

ENTRYPOINT ["/opt/app/stage/bin/app"]

EXPOSE 9000

Build Docker Image

Build the image and name it pizzeria:

docker build -t pizzeria .

This will take a while. You will need to get more than a cup of tea.

Run the new image and map the port to 9020.

docker run -ti --rm -p 9000:9020 pizzeria:latest

In another terminal window:

curl localhost:9020

Pizzeria service

Lets create the actual pizzeria service. We will create a normal landing page that in our case shows the pizza menu and order form. We will have a POST endpoint to send our order to, and a confirmation page to confirm our pizza order has been received.

Though first lets remove some parts we are not using in this application. (of course in your future applications you may use these, so be aware of them)

rm -f app/controllers/AsyncController.scala;
rm -f app/controllers/CountController.scala;
rm -rf app/filters app/services;
rm -f app/Filters.scala app/Module.scala;
rm -rf bin libexec;
rm -rf test

Note: There is an example pizzeria project on my Github.

In the project's build.sbt file lets remove some dependencies we won't need in this example (cache, ws, scalatest), keeping jdbc as we will need that later. And also add a hack with devnull to support docker restarts.

vi build.sbt name := """pizzeria"""

version := "1.0-SNAPSHOT"

lazy val root = (project in file(".")).enablePlugins(PlayScala)

scalaVersion := "2.11.8"

libraryDependencies ++= Seq(
  jdbc
)

javaOptions in Universal ++= Seq(
  "-Dpidfile.path=/dev/null"
)

Show menu

Lets edit the routes file. We will remove the references to Async, Home and CountControllers. And add a PizzaController. Open conf/routes in your editor of choice.

vi conf/routes # Routes
# ~~~~

GET   /                 controllers.PizzaController.showMenu

GET   /assets/*file     controllers.Assets.versioned(path="/public", file: Asset)

You can cheat a little by just renaming HomeController to PizzaController, then editing it.

mv -f app/controllers/HomeController.scala \
  app/controllers/PizzaController.scala;
vi app/controllers/PizzaController.scala

Lets rename the class, and then rename the index function to showMenu, and return the pizzamenu template.

package controllers

import javax.inject._
import play.api._
import play.api.mvc._

@Singleton
class PizzaController @Inject() () extends Controller {

  def showMenu = Action {
    Ok(views.html.pizzamenu)
  }

}

Again we can cheat by renaming the index template, and removing some cruft.

mv -f app/views/index.scala.html app/views/pizzamenu.scala.html;
vi app/views/pizzamenu.scala.html

@()

@main("Pizza Menu") {

  <h1>Pizza Menu</h1>

}

Running the app, and testing it in another terminal or browser should display the "Pizza Menu" headline.

sbt run curl localhost:9000

Ok that is nice, but there is still no actual pizzas shown! Lets create the domain object:

mkdir app/models;
touch app/models/pizza.scala;
vi app/models/pizza.scala
package models

case class Pizza(name: String)

And a repository lookup, this could query a database, or use some connector/adapter to talk to some middleware or backend services.

mkdir app/repositories;
touch app/repositories/PizzaRepository.scala;
vi app/repositories/PizzaRepository.scala
package repositories

import com.google.inject.ImplementedBy
import javax.inject.Inject
import play.api.libs.concurrent.Execution.Implicits._
import scala.concurrent.Future
import models._


@ImplementedBy(classOf[DefaultPizzaRepository])
trait PizzaRepository {

  def findPizzas() = Future {
    List( Pizza("Hawaii"), Pizza("Pepperoni") )
  }

}

class DefaultPizzaRepository @Inject() () extends PizzaRepository

The ImplementedBy and Inject is the additional cruft required by the Guice runtime dependency injection. Lets inject the repository into the pizza controller:

vi app/controllers/PizzaController.scala package controllers

import javax.inject._
import play.api._
import play.api.libs.concurrent.Execution.Implicits._
import play.api.mvc._
import repositories._

@Singleton
class PizzaController @Inject() (val pizzaRepository: PizzaRepository)
extends Controller {

  def showMenu = Action.async {
    pizzaRepository.findPizzas().map{ pizzas =>
      Ok(views.html.pizzamenu(pizzas))
    }
  }

}

You will notice the .async postfix to the Action. This means this will return a future of result, so not blocking, and is because the call to the repository returns a future as well.

We need to modify the pizza menu view as we now pass in a list of pizzas.

vi app/views/pizzamenu.scala.html

@(pizzas: List[Pizza])

@main("Pizza Menu") {

  <h1>Pizza Menu</h1>

  <ul>
    @for(pizza <- pizzas){
      Pizza: @pizza.name
    }
  </ul>


}

This should now list the pizzas in the application:

sbt run curl localhost:9000

Order pizza

Now lets order the pizza!

First lets modify the menu view.

vi app/views/pizzamenu.scala.html @(pizzas: List[Pizza])

@import helper._

@main("Pizza Menu") {

  <h1>Pizza Menu</h1>

  <ul>
    @for(pizza <- pizzas){
      @form(action=routes.controllers.PizzaController.orderPizza){
        Pizza: @pizza.name
        <button type="submit">order</button>
        <input type="hidden" name="pizza.name" value="@pizza.name"/>
      }
    }
  </ul>

}

Then reflect the new orderPizza call in the routes file:

vi conf/routes # Routes
# ~~~~

GET   /           controllers.PizzaController.showMenu
POST  /order      controllers.PizzaController.orderPizza

GET   /assets/*file     controllers.Assets.versioned(path="/public", file: Asset)

And create the orderPizza function in the pizza controller to simple return a success page.

vi app/controllers/PizzaController.scala .....

  def orderPizza = Action {
    Ok
  }

.....

You can test the order button now, it should respond in a blank success page.

When ready lets create a pizza order domain object in our models file:

vi app/models/pizza.scala case class PizzaOrder(id: Option[Long], pizza: Pizza)

And in the controller a form mapping transformer.

vi app/controllers/PizzaController.scala package controllers

import javax.inject._
import play.api._
import play.api.data._
import play.api.data.Forms._

import play.api.libs.concurrent.Execution.Implicits._
import play.api.mvc._
import scala.concurrent.Future
import models._
import repositories._

@Singleton
class PizzaController @Inject() (val pizzaRepository: PizzaRepository)
extends Controller {

  val orderForm = Form( mapping (
      "id" -> ignored(None: Option[Long]),
      "pizza" -> mapping (
        "name" -> nonEmptyText
      )(Pizza.apply)(Pizza.unapply)
    )(PizzaOrder.apply)(PizzaOrder.unapply)
  )

.....

}

Then we extend the orderPizza method to bind the request to this mapping.

.....

def orderPizza = Action { implicit request =>
  orderForm.bindFromRequest.fold(
    errors => BadRequest,
    order  => Created
  )

}

.....

This can now be tested via a browser by clicking on the order buttons on the menu, or directly via curl.

This still doesn't really do anything, it just returns blank pages as we havent specified a template. It only returns the expected http error code. We could also include the erroneous form in the BadRequest response but we are keeping it simple for now.

curl --data "{\"wrongfield\"=\"wrongdata\"}" \
  -H "Content-Type: application/json" \
  -v http://localhost:9000/order

This should not work as wrong field name, and will return amongst others:

< HTTP/1.1 400 Bad Request

But this should order a pizza:

curl --data "{\"pizza.name\":\"Hawaii\"}" \
  -H "Content-Type: application/json" \
  -v http://localhost:9000/order
< HTTP/1.1 201 Created

Confirm order

Ok lets show a more usefull pizza ordered confirmation page, than just a blank page with http status code.

Lets add a confirmation page route, which includes an order id in its path.

vi conf/routes # Routes
# ~~~~

GET  /               controllers.PizzaController.showMenu
POST /order          controllers.PizzaController.orderPizza
GET  /order/:orderId controllers.PizzaController.showConfirmation(orderId: Long)

GET  /assets/*file   controllers.Assets.versioned(path="/public", file: Asset)

Lets add the showConfirmation method to PizzaController. It will try to find the order, and either display a confirmation page with it, or a blank page with a 404 not found http status code if there was no orders found with the supplied id.

vi app/controllers/PizzaController.scala .....

   def showConfirmation(orderId: Long) = Action.async {
     pizzaRepository.findPizzaOrder(orderId).map{
       case Some(order) => Ok(views.html.confirmation(order))
       case _           => NotFound
    }
  }

.....

We will need to create the findPizzaOrder method in the pizza repository, and hard code its response for now:

vi app/repositories/PizzaRepository.scala .....

  def findPizzaOrder(orderId: Long) = Future {
    Some(PizzaOrder(Some(orderId), Pizza("Hawaii")))
  }

.....

And the view

cp app/views/pizzamenu.scala.html \
  app/views/confirmation.scala.html;
vi app/views/confirmation.scala.html
@(pizzaOrder: PizzaOrder)

@main("Pizza order confirmation") {

  <h1>Pizza ordered!</h1>

  <p>
    You have ordered a <em>@pizzaOrder.pizza.name</em> pizza!
  </p>

}

You can test this via:

curl localhost:9000/order/123

Lets add a correct response form the order post we created before, and a call to the repositry to simulate storing the order. Edit the orderPizza in the pizza controller:

vi app/controllers/PizzaController.scala .....

  def orderPizza = Action.async { implicit request =>
    orderForm.bindFromRequest.fold(
      errors => Future.successful( BadRequest ),
      order  => {
        pizzaRepository.addPizzaOrder(order) map {
          case PizzaOrder(Some(orderId), _) =>
            Redirect(routes.PizzaController.showConfirmation(orderId))
          case _ => InternalServerError("Could not add pizza order")
        }
      }

    )
  }

.....

And then add the addPizzaOrder to the pizza repository:

vi app/repositories/PizzaRepository.scala .....

  def addPizzaOrder(pizzaOrder: PizzaOrder): Future[PizzaOrder] = Future {
    pizzaOrder.copy(id = Some(123L)
  }

.....

Test the order pizza call:

curl --data "{\"pizza.name\":\"Hawaii\"}" \
  -H "Content-Type: application/json" \
  -v http://localhost:9000/order

Which response should now include a redirect to the confirmation page:

.....

< HTTP/1.1 303 See Other
< Location: /order/123

.....

Persist

The hard coded responses in PizzaRepository is not satisfactory. Lets add some persistant database support.

For this we will use the H2 in-memory database. Which is ideal unit tests and when developing locally.

Then perhaps a docker based PostgreSQL database when developing and testing a larger stack via Docker Compose and in staging environments. See my Docker machine & compose pizzeria howto for an example.

When in production it is recommend using proper database instance(s) outside Docker. For example clustered PostgreSQL, Amazon RDS, Google Bigtable, Redis on Heroku, Cassandra on Mesos etc. I am on the fence on this one, I understand why due to Docker's ephemeral nature and historic issue with data persistence, but I do run many databases in containers, especially less ciritical system...

For persistance library this Pizzeria uses Anorm. Anorm is very thin wrapper around normal SQL made by the original Play developers themselves. A more common framework is Slick, and is the default for Play these days. Slick is functional relational mapper, and often involves more compexity that is needed by too many layers of abstractions.

In the project's build.sbt file we will add some dependencies we will need, and actually use the jdbc alias we kept earlier.

vi build.sbt .....

libraryDependencies ++= Seq(
  jdbc,
  evolutions,
  "com.h2database"    %  "h2"        % "1.4.192",
  "com.typesafe.play" %% "anorm"     % "2.5.0"
)

.....

Play's default main configuration file has evolved into a huge blob of configurations. But lets remove 99% which is just cruft, add some jdbc properties, and end up with a config file like this:

vi conf/application.conf ## Secret key
play.crypto.secret = "changemechangemechangemechangeme"
play.crypto.secret=${?APPLICATION_SECRET}

## Evolutions
play.evolutions.db.default.autoApply = true

## JDBC Datasource
db.default {
  driver = org.h2.Driver
  url = "jdbc:h2:mem:play;MODE=PostgreSQL;DB_CLOSE_DELAY=-1;DATABASE_TO_UPPER=FALSE"
  username = sa
  password = ""
  logSql = true
}  

Above we have enabled evolutions which lets us create and evolve our database schema and data. Our first evolution file creates the initial schema:

mkdir -p conf/evolutions/default;
touch conf/evolutions/default/1.sql;
vi conf/evolutions/default/1.sql
# Pizzeria schema

# --- !Ups

CREATE TABLE pizza (
  id serial NOT NULL,
  name varchar(128) NOT NULL,
  PRIMARY KEY (id)
);

CREATE TABLE pizza_order (
  id serial NOT NULL,
  pizza_id int NOT NULL REFERENCES pizza (id),
  order_date TIMESTAMP NOT NULL DEFAULT NOW(),
  PRIMARY KEY (id)
);


# --- !Downs

DROP TABLE pizza_order;
DROP TABLE pizza;

Our PizzaRepository can now be modified to use an actual database.

vi app/repositories/PizzaRepository.scala package repositories

import anorm._
import anorm.SqlParser._
import com.google.inject.ImplementedBy
import javax.inject.Inject
import play.api.db._
import play.api.libs.concurrent.Execution.Implicits._
import scala.concurrent.Future
import models._


@ImplementedBy(classOf[DefaultPizzaRepository])
trait PizzaRepository {

  def dbApi: DBApi

  private lazy val db: Database = dbApi.database("default")

  .....

}

class DefaultPizzaRepository @Inject() (val dbApi: DBApi) extends PizzaRepository

Modify the existing trait's methods, starting with findPizzas:

.....

  val pizzaParser: RowParser[Pizza] = Macro.namedParser[Pizza]

  def findPizzas(): Future[List[Pizza]] = Future {
    db.withConnection { implicit connection =>
       SQL"""
             select name from pizza order by name
          """
          .as( pizzaParser.* )
    }
  }

.....

And a much more comples addPizzaOrder that uses internal functions (mostly as we have not exposed the database id in the Pizza domain model):

.....

  def addPizzaOrder(pizzaOrder: PizzaOrder): Future[PizzaOrder] = {

    def findPizzaId(pizzaName: String) = Future {
      db.withConnection { implicit connection =>
        SQL"""
              select id from pizza where name = $pizzaName
            """
           .as( scalar[Long].single )
      }
    }
    
    def addOrder(pizzaId: Int) = Future {
      db.withConnection { implicit connection =>
        SQL"""
              insert into pizza_order(pizza_id) values ($pizzaId)
           """
           .executeInsert()
      }
    }

    for {
      pizzaId <- findPizzaId(pizzaOrder.pizza.name)
      orderId <- addOrder(pizzaId)
    } yield pizzaOrder.copy( id = orderId )
  }

.....

And finally findPizzaOrder:

.....

  def findPizzaOrder(orderId: Long): Future[Option[PizzaOrder]] = Future {
    db.withConnection { implicit connection =>
       SQL"""
             select p.id as pizza_id, p.name as pizza_name
             from pizza_order o
             inner join pizza p on p.id = o.pizza_id
             where o.id = $orderId
          """
          .as( ( get[Long]("pizza_id") ~
                 get[String]("pizza_name")
             ).singleOpt )
          .map{ case pizzaId ~ pizzaName =>
            PizzaOrder( Some(orderId), Pizza(pizzaName) )
          }
    }
  }

.....

Lets add the default pizzas to the menu:

touch conf/evolutions/default/2.sql;
vi conf/evolutions/default/2.sql
# Pizzeria Menu

# --- !Ups

INSERT INTO pizza (name) values
  ('Margherita'),
  ('Hawaii'),
  ('Quattro Stagioni'),
  ('Pepperoni');


# --- !Downs

DELETE FROM pizza;

We can now test the full flow with persisted data.

sbt run curl localhost:9000

This should show the extended menu.

curl --data "{\"pizza.name\":\"Margherita\"}" \
  -H "Content-Type: application/json" \
  -v localhost:9000/order/code

This should show a redirect to confimation page. And if we GET that it should show we ordered a Margherita.

curl localhost:9000/order/1

Build a Docker image of our finished Pizzeria service. And tag it.

docker build -t pizzeria .;
docker tag pizzeria:latest pizzeria:1.0

Docker Cloud

Docker Cloud is a handy way to manage your public docker containers. It enables you to easily build and host your images. It lets you orchestrate and deploy your container stacks. And monitor and scale as appropriate.

Docker Cloud does not actually host your actual running containers. Hosting is still done with normal IAAS cloud providers. Such as Amazon AWS, etc. Docker Cloud instead manages the containers on these instances for you. It can directly create instances for you on some of the IAAS providers, or you can connect an existing node to Docker Cloud by installing an agent service. You can also spread your containers horizontally across nodes on different IAAS providers.

There is a reasonable free tier that lets your experiment and run quite a few containers for free. (As I am grand-fathered in as a user of Docker Cloud's predecessor, Tutum so my free tier is slightly more generous). Combine this with the free tiers at many IAAS providers you can experiment for quite some time totally free.

Repository

We need to store the Docker image of our Pizzeria service, so we need a Docker registry There are many providers, Quay.io, Amazon EC2 Container Registry, Google Platform Container Registry, etc. You can even host a registry yourself inside a container. But for our pizzeria we will use the Docker Hub.

Go to Docker Hub, log in with your Docker id or sign up. Create a new repository for the pizzeria service.

Lets push up our local Docker image to the public one you just created.

docker push yourUsername/pizzeria:latest

Node(s)

As part of the excellent Docker Cloud's own intro to itself we will start by integrating your IAAS / cloud service provider. At the time of writing, December 2016, Docker Cloud supports out of the box AWS, Digital Ocean, Microsoft Azure, SoftLayer and Packet.net. It does not yet support Google Cloud directly.

DigitalOcean is the easiest to integrate with. Amazon AWS I would guess is the most popular as many will already have a presence there. I prefer Google Cloud as provider, via the Bring Your Own Node feature.

Create a node or two. Keep in mind the pizzeria service is a JVM based image so will be memory hungry, and for now compute shy. So the t2.nano and t2.micro instances on AWS will be too small where as a $20 instance on DigitalOcean or a t2.small instance on AWS is a good start, and R4 instances better suited later on. For example I mostly use 6.5GB with 1 cpu custom instances on Google Cloud to host 4-5 containers per node.

Note unless you are within the free tier on your IAAS, the node choices will cost you money.

Service

Create a service, which is basically your application. There are lots of options you dont need to worry about right now but essentialy:

  • Specify the docker image and version to use (the latest of the pizzeria repository)
  • Think of a nickname for the service (pizzeria)
  • Set how many containers to scale across (1 for now)
  • Toggle whether to auto deploy updates to the image tag (yes)
  • Decide whether to expose a port as published, and if that is dynamic or static.
    (We will publish port 9010 for our pizzeria).

Deploy

Once the service is configured you press the Create & Deploy button.

If that works, then you should be forwarded to the final screen where a summary of the service is shown. Note the Endpoints section, where you shold have a Service and a Container endpoints. The service endpoint should look something like:

tcp://pizzeria.12abcd34.svc.dockerapp.io:9010

The service endpoint is clickable, but change the protocol from tcp to http.

Note: If you plan to add SSL to your apps by following my Let's Encrypt with Nginx, then this service endpoint is the one you need to proxy to.

Stack

Stacks are not need in this pizzeria example. But if you were to separate the service into e.g. a back-end, front-end and database, like my docker compose based example, then Stacks is where you define the separate services within Docker Cloud.

Alternatives

Spray / Akka HTTP

Spray and its successor Akka HTTP are alternatives to Play. It is a good choice for a service without UI such as the backend in my docker compose example.

Heroku

Heroku is a PAAS. And is the smoothest and quiest way to deploy a pizzeria service to a cloud/hosting provider. I love Heroku, and run many applications with it, but it is not perfect. They do support a Docker build flow, but mostly you do not use Docker with Heroku. Instead you push your code (via Git remote) and use their build flow to generate and host their own containers.

Amazon ECS

The EC2 Container Service is a full and extensive Docker container service.

Google Container Engine

The Container Engine is an extensive Docker container service. It is a commercial offering that uses Kubernetes clusters.

Tectonic

Tectonic is a commercial offering by CoreOS that uses Kubernetes clusters.

Cloud 66

Cloud 66 offer Docker management of containers on other IAAS providers.

Triton

Triton by Joyent offer containers as a service.

Docker Machine

At a lower level Docker Machine lets you create instances in cloud providers. You can then manually deploy your containers to it. You can also scale horizontally with Docker Swarm.

Docker for AWS

A recently annouced product you can also deploy containers directly to AWS with Docker for AWS.

Feedback

Please fork and send a pull request for to correct any typos, or useful additions.

Buy a t-shirt if you found this guide useful. Hire me for short term advice or long term consultancy.

Otherwise contact me. Especially for things factually incorrect. Apologies for procrastinated replies.