Play Framework is a reactive web framework for Scala and Java that is built on Akka and Akka Streams. It provides a stateless, non-blocking architecture ideal for building scalable web applications and APIs. This guide covers building and deploying a Play Scala application on a VPS.
Project Setup
# Install sbt
curl -fL https://github.com/sbt/sbt/releases/download/v1.10.0/sbt-1.10.0.tgz | tar xz
sudo mv sbt /opt/
export PATH=$PATH:/opt/sbt/bin
# Create new Play project
sbt new playframework/play-scala-seed.g8
cd play-scala-seed
Application Structure
app/
├── controllers/
│ └── HomeController.scala
├── models/
│ └── User.scala
├── services/
│ └── UserService.scala
├── views/
│ └── index.scala.html
conf/
├── application.conf
├── routes
└── logback.xml
build.sbt
project/
└── plugins.sbt
Controller Example
// app/controllers/UserController.scala
package controllers
import javax.inject._
import play.api.mvc._
import play.api.libs.json._
import scala.concurrent.{ExecutionContext, Future}
case class User(id: Long, name: String, email: String)
object User {
implicit val format: OFormat[User] = Json.format[User]
}
case class CreateUser(name: String, email: String)
object CreateUser {
implicit val reads: Reads[CreateUser] = Json.reads[CreateUser]
}
@Singleton
class UserController @Inject()(
cc: ControllerComponents
)(implicit ec: ExecutionContext) extends AbstractController(cc) {
def list(): Action[AnyContent] = Action.async {
val users = List(
User(1, "Alice", "alice@example.com"),
User(2, "Bob", "bob@example.com")
)
Future.successful(Ok(Json.toJson(users)))
}
def get(id: Long): Action[AnyContent] = Action.async {
Future.successful(Ok(Json.toJson(User(id, "Alice", "alice@example.com"))))
}
def create(): Action[JsValue] = Action.async(parse.json) { request =>
request.body.validate[CreateUser].fold(
errors => Future.successful(BadRequest(Json.obj("error" -> JsError.toJson(errors)))),
input => {
val user = User(3, input.name, input.email)
Future.successful(Created(Json.toJson(user)))
}
)
}
}
// conf/routes
GET /health controllers.HomeController.health
GET /api/users controllers.UserController.list
GET /api/users/:id controllers.UserController.get(id: Long)
POST /api/users controllers.UserController.create
Production Configuration
# conf/application.conf
play.http.secret.key = ${?APPLICATION_SECRET}
play.server.http.port = ${?PORT}
# Database
db.default.driver = org.postgresql.Driver
db.default.url = ${?DATABASE_URL}
db.default.hikaricp.maximumPoolSize = 20
# Production settings
play.filters.enabled += "play.filters.gzip.GzipFilter"
play.filters.enabled += "play.filters.headers.SecurityHeadersFilter"
# Allowed hosts
play.filters.hosts.allowed = ["example.com", "localhost:9000"]
Building for Production
# Create production distribution
sbt dist
# Output: target/universal/play-scala-seed-1.0.zip
# Or create a Docker image
sbt Docker/publishLocal
# Copy to VPS and unzip
scp target/universal/play-scala-seed-1.0.zip user@vps:/opt/
ssh user@vps "cd /opt && unzip play-scala-seed-1.0.zip"
Running in Production
# Start with production settings
/opt/play-scala-seed-1.0/bin/play-scala-seed \
-Dplay.http.secret.key="$(head -c 32 /dev/urandom | base64)" \
-Dconfig.resource=production.conf \
-J-Xmx512m \
-J-Xms512m
Systemd Service
[Unit]
Description=Play Framework Application
After=network.target postgresql.service
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/play-app
ExecStart=/opt/play-app/bin/play-scala-seed -Dconfig.resource=production.conf -J-Xmx512m
EnvironmentFile=/opt/play-app/.env
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target
Docker Deployment
# build.sbt
dockerBaseImage := "eclipse-temurin:21-jre-alpine"
dockerExposedPorts := Seq(9000)
dockerUpdateLatest := true
# Build and run
sbt Docker/publishLocal
docker run -p 9000:9000 -e APPLICATION_SECRET=secret play-app:latest
Summary
Play Framework brings reactive programming to Scala web development with its non-blocking, stateless architecture. The combination of strong typing, pattern matching, and Akka actors provides a robust foundation for building scalable applications. sbt dist creates a self-contained deployment package, and the framework integrates naturally with the JVM ecosystem for enterprise environments. For Scala teams, Play remains the most mature and production-ready web framework option.