Gleam Project Deployment
Deploying a Gleam application means packaging your code, compiling it for production, and running it in a reliable environment. Gleam inherits Erlang's excellent deployment story — including self-contained releases, live code upgrades, and robust process supervision.
Deployment Overview
Deployment Options
──────────────────────────────────────────────────
Option 1: Docker container
→ Most common, works everywhere
→ Package app + Erlang runtime into one image
Option 2: Erlang release (via rebar3 or mix)
→ Self-contained binary with embedded BEAM
→ No Erlang needed on the server
Option 3: Fly.io / Render / Railway
→ Platform-as-a-service with Docker support
→ Easiest for small teams
Option 4: Direct on VPS
→ Install Erlang + Gleam on the server
→ Run with gleam run or compiled release
Production Build
Development build (default):
──────────────────────────────────────────────────
gleam build
→ Fast compile, debug info included
→ Not optimized for production
Production build:
──────────────────────────────────────────────────
gleam build # Gleam always compiles fully
# BEAM handles runtime optimization
The BEAM JIT compiler optimizes code at runtime. Unlike many languages, there is no separate "production compile flag" — the BEAM automatically performs optimizations during execution.
Docker Deployment
A multi-stage Dockerfile keeps the final image small:
# Stage 1: Build
FROM ghcr.io/gleam-lang/gleam:v1.5.1-erlang-alpine AS builder
WORKDIR /app
COPY gleam.toml manifest.toml ./
RUN gleam deps download
COPY src/ src/
RUN gleam build
# Stage 2: Runtime
FROM erlang:26-alpine AS runner
WORKDIR /app
# Copy compiled BEAM files
COPY --from=builder /app/build/dev/erlang /app/build/dev/erlang
COPY --from=builder /root/.cache/gleam /root/.cache/gleam
EXPOSE 8080
CMD ["erl", "-pa", "/app/build/dev/erlang/*/ebin", \
"-eval", "my_app:main()", "-noshell"]
Docker Build and Run
──────────────────────────────────────────────────
docker build -t my_gleam_app .
docker run -p 8080:8080 my_gleam_app
Environment Variables
Read configuration from environment variables at startup:
@external(erlang, "os", "getenv")
fn os_getenv(name: charlist) -> charlist
pub fn get_env(name: String) -> Option(String) {
let result = os_getenv(erlang.to_charlist(name))
case erlang.from_charlist(result) {
"" -> None
val -> Some(val)
}
}
pub fn main() {
let port =
get_env("PORT")
|> option.then(int.parse >> option.from_result)
|> option.unwrap(8080)
let host = get_env("HOST") |> option.unwrap("0.0.0.0")
start_server(host, port)
}
Deploying to Fly.io
fly.toml
──────────────────────────────────────────────────
app = "my-gleam-app"
primary_region = "sin" # Singapore
[build]
dockerfile = "Dockerfile"
[http_service]
internal_port = 8080
force_https = true
[http_service.concurrency]
type = "requests"
hard_limit = 200
[env]
DATABASE_URL = "" # set via: fly secrets set DATABASE_URL=...
Fly.io Deployment Commands
──────────────────────────────────────────────────
fly launch # first-time setup
fly deploy # deploy latest build
fly logs # stream live logs
fly ssh console # SSH into running instance
fly secrets set KEY=VALUE # set environment secrets
Erlang Releases
An Erlang release bundles your application with the BEAM runtime into one directory. Copy it to any compatible Linux server and run it — no Erlang installation required on the server.
Creating a Release (using rebar3)
──────────────────────────────────────────────────
1. gleam build # compile to BEAM files
2. rebar3 release # package into a release
3. ./_build/default/rel/my_app/bin/my_app start
Release directory:
bin/my_app → start/stop/restart scripts
releases/1.0.0/ → application code
lib/ → all dependencies
erts-x.x.x/ → Erlang runtime (embedded)
Process Monitoring in Production
BEAM Built-in Introspection
──────────────────────────────────────────────────
# Connect to running node:
erl -sname debug@localhost -remsh my_app@server
# Inside the Erlang shell:
observer:start(). # GUI process monitor
:erlang.memory() # memory usage
:erlang.system_info(:process_count) # live process count
:sys.get_state(pid) # actor state inspection
Health Check Endpoint
import wisp
pub fn router(req: wisp.Request) -> wisp.Response {
case wisp.path_segments(req) {
["health"] ->
wisp.json_response("{\"status\":\"ok\"}", 200)
["ready"] ->
case check_all_services_ready() {
True -> wisp.json_response("{\"ready\":true}", 200)
False -> wisp.json_response("{\"ready\":false}", 503)
}
_ -> main_router(req)
}
}
Logging in Production
Logging Strategy
──────────────────────────────────────────────────
Development: io.println() is fine
Production: Use structured logging (JSON)
Recommended package: gleam_stdlib + custom logger
pub fn log(level: String, message: String, context: Dict(String, String)) -> Nil {
let fields = dict.fold(context, [], fn(acc, k, v) {
["\"" <> k <> "\":\"" <> v <> "\"", ..acc]
})
let json = "{\"level\":\"" <> level <> "\",\"msg\":\"" <> message <> "\","
<> string.join(fields, ",") <> "}"
io.println(json)
}
Graceful Shutdown
import gleam/erlang/process
import gleam/otp/supervisor
pub fn main() {
let assert Ok(sup) = start_application_supervisor()
// Handle OS signals for graceful shutdown
process.trap_exits(True)
// Block until shutdown signal
process.receive_forever()
// Supervisor handles child cleanup automatically
supervisor.stop(sup)
}
Key Points
Deployment Essentials
──────────────────────────────────────────────────
1. Docker is the simplest deployment target
2. Multi-stage Dockerfile: build → small runtime image
3. Read config from environment variables (PORT, HOST, etc.)
4. Fly.io, Render, Railway support Gleam/Docker easily
5. Erlang releases embed the runtime — no server dependencies
6. OTP supervisors handle restarts automatically in production
7. BEAM observer and shell give live production introspection
8. Add /health endpoint for load balancer health checks
Deploying Gleam is as straightforward as deploying any Erlang application. The BEAM's decades of production experience means your deployment story comes with battle-tested patterns for zero-downtime updates, live debugging, and automatic process recovery — features that many modern frameworks are still working toward.
