This repository is a ready-to-open Scala 3 and Play Framework workspace for VS Code Dev Containers. It gives newcomers the same local environment every time: a JDK, Scala tools, sbt, Metals editor support, formatting, debugging, tests, and a production Docker image path.
Open the folder in VS Code and run Dev Containers: Reopen in Container.
Inside the container:
scala3-compiler -version
sbt -Dsbt.batch=true runThen visit:
http://localhost:9000/
Try the JSON endpoints:
curl http://localhost:9000/api/health
curl http://localhost:9000/api/hello/ScalaBuild and run the application image:
docker build -t play-devcontainer .
docker run --rm -p 9000:9000 play-devcontainerFor real deployments, set a strong secret instead of using the demo fallback:
docker run --rm -p 9000:9000 \
-e PLAY_HTTP_SECRET_KEY="$(openssl rand -hex 32)" \
play-devcontainerScala development in this repository has three layers:
- The container provides the operating system, JDK, Node.js, Codex, Coursier, Scala CLI, sbt, and Metals MCP.
- The Play project provides controllers, routes, configuration, sbt build definition, dependency resolution, packaging, formatting, and tests.
- VS Code and Metals provide editor features such as diagnostics, go-to definition, completion, build import, and debugging.
The editor does not compile Scala by itself. VS Code talks to Metals, Metals talks to a build server through BSP, and the build server uses sbt to compile and understand the project.
Scala runs on the JVM, so the Java Development Kit is the foundation of the environment. The devcontainer uses Eclipse Temurin JDK 26. The production image uses a JDK while building and a smaller JRE while running.
The devcontainer also installs JDK source files into src.zip so editor
navigation can jump into Java standard library classes.
build.sbt sets:
ThisBuild / scalaVersion := "3.8.3"That version is used by sbt for the main project. The container also installs
scala3-compiler so you can check the compiler directly.
This project uses Play Framework 3.0.10, the latest stable Play 3 release at
the time this starter was converted. The Play sbt plugin adds the standard Play
layout and tasks:
app/: Scala controllers and Twirl viewsconf/routes: HTTP route definitionsconf/application.conf: application configurationtest/: Play controller and route testssbt run: starts the development server on port9000sbt stage: builds a runnable production layout undertarget/universal/stage
The starter includes:
GET /: a simple HTML welcome pageGET /api/health: JSON health checkGET /api/hello/:name: JSON greeting endpoint
sbt is the main build tool for this repository. It is responsible for:
- reading
build.sbt - reading
project/build.properties - downloading Scala, Play, plugins, and library dependencies
- compiling routes, Twirl templates, and Scala code
- running the Play development server with
sbt run - running tests with
sbt test - building the production stage with
sbt stage - checking and applying formatting with
sbt scalafmtCheckAllandsbt scalafmtAll - exposing project metadata to Metals through BSP
The project pins sbt in project/build.properties:
sbt.version=1.12.11The devcontainer creates /usr/local/bin/sbt as a small Coursier-based wrapper
instead of storing a launcher script in the repository.
Coursier is the dependency and tool installer used by this setup. It downloads Scala tools and JVM artifacts into the Coursier cache, normally under:
/home/vscode/.cache/coursier
The devcontainer uses Coursier to install:
scala3-compilerscala-climetals-mcp- the sbt launcher used by the
sbtwrapper
Scala CLI is available for scripts and experiments, but sbt is the project build tool for this Play application.
Metals is the Scala language server. It powers diagnostics, completion, go-to definition, symbol search, code actions, build import, and debug integration in VS Code.
BSP means Build Server Protocol. It is the bridge between Metals and sbt.
Generated .bsp files are ignored by git and should not be edited by hand.
.scalafmt.conf configures Scalafmt 3.10.7 with the Scala 3 dialect:
sbt -Dsbt.batch=true scalafmtCheckAll
sbt -Dsbt.batch=true scalafmtAllbuild.sbt enables SemanticDB so Metals and other tools can use richer semantic
information.
build.sbt: pins Scala3.8.3, enables SemanticDB, enablesPlayScala, and declares Play/test dependencies.project/build.properties: pins sbt1.12.11.project/plugins.sbt: declares the Play sbt plugin andsbt-scalafmt.app/controllers/HomeController.scala: example Play controller.app/views/index.scala.html: HTML welcome page.conf/routes: route table for the HTML page and JSON endpoints.conf/application.conf: Play application configuration.test/controllers/HomeControllerSpec.scala: controller and route tests..vscode/launch.json: Metals/Scala debug launch config for Play.Dockerfile: production multi-stage build that runssbt stage.
This is the entry point for VS Code Dev Containers. It defines the devcontainer name, development Dockerfile, remote user, mounted host directories, VS Code extensions, and lifecycle commands.
This builds the interactive development environment. It installs Eclipse Temurin
JDK 26, basic OS tools, Node.js 26, Codex, a non-root vscode user, JDK
sources, Coursier, Scala compiler, Scala CLI, Metals MCP, and an sbt wrapper.
This script repairs permissions, configures Git, syncs selected host SSH/Codex
config, configures Codex Metals MCP, and starts metals-mcp on port 8421.
The repository root Dockerfile is separate from the devcontainer Dockerfile.
It builds a runnable Play application image, not an editor environment.
It has two stages:
build: installs Coursier and sbt, copies build metadata, downloads dependencies, copies Play sources/config/tests, and runssbt stage.- runtime: starts from a smaller JRE image, copies
target/universal/stage, exposes port9000, and runs the generated Play launcher.
The image uses these runtime settings:
PORT: defaults to9000PLAY_HTTP_SECRET_KEY: defaults to a long local-demo-only value
The fallback secret is only for local demos. Set PLAY_HTTP_SECRET_KEY in any
real environment.
Run sbt commands serially. Starting multiple sbt processes at the same time can
hit the sbt boot socket lock and fail with ServerAlreadyBootingException.
# Start the Play development server
sbt -Dsbt.batch=true run
# Compile
sbt -Dsbt.batch=true compile
# Run tests
sbt -Dsbt.batch=true test
# Check formatting
sbt -Dsbt.batch=true scalafmtCheckAll
# Build the production stage
sbt -Dsbt.batch=true stage
# Build and run the production image
docker build -t play-devcontainer .
docker run --rm -p 9000:9000 play-devcontainer
# Check installed tool versions
java -version
scala3-compiler -version
scala-cli --version
sbt sbtVersionThe following directories are generated by tools and should normally not be edited manually:
.bsp/: build server connection files.metals/: Metals workspace data and logs.scala-build/: Scala CLI build datatarget/: sbt and compiler outputproject/target/: sbt build-project outputproject/project/: nested sbt build metadata
They are ignored by .gitignore. .dockerignore also excludes them from Docker
build context so images do not include local build caches or editor state.
If Metals does not import the build, run:
sbt -Dsbt.batch=true compileThen use the VS Code command Metals: Import build.
If generated directories have permission problems, restart the devcontainer. The startup script repairs ownership and write permissions for the common build directories.
If Codex cannot reach Metals MCP, check:
cat .metals/mcp/metals-mcp.logThe default MCP endpoint is:
http://127.0.0.1:8421/mcp