Deploy Erlang to Europe — EU Hosting for Erlang/OTP Backends in 2026
Erlang is one of the few programming languages that can claim to have kept large parts of the world's telecommunications infrastructure running for four decades. It was designed at Ericsson — the Swedish company headquartered in Stockholm — in the mid-1980s, specifically to solve problems that general-purpose languages could not: how do you build a telephone switch that handles millions of concurrent calls, never loses a connection, and can be updated without a maintenance window?
The answer was a language with first-class concurrency, message-passing semantics, and a supervision architecture that assumes processes will fail and recovers from those failures automatically. The result is what the Erlang community calls nine nines availability — 99.9999999% uptime. That figure comes from AXD301, Ericsson's ATM switch, which ran on Erlang/OTP in production and achieved less than 32 milliseconds of downtime per year.
Today Erlang powers systems at Klarna (Swedish FinTech, largest BNPL provider in Europe), WhatsApp (before Meta, at 2 million connections per server), RabbitMQ (British messaging infrastructure, now running at millions of EU deployments), and CouchDB. For EU teams building real-time systems, chat infrastructure, IoT backends, or FinTech APIs that cannot afford downtime, Erlang's European origins are matched by its engineering guarantees.
This guide shows how to deploy Erlang/OTP applications — using the Cowboy HTTP server — to EU infrastructure with sota.io.
Why Erlang for Production Backends
The core argument for Erlang is not about syntax or developer ergonomics. It is about a concurrency and reliability model that is structurally different from anything in the mainstream.
The actor model, natively. Every Erlang process is an actor: isolated state, message passing, no shared memory. Spawning a million Erlang processes is routine — each process uses a few hundred bytes of memory and is scheduled by the Erlang VM's own scheduler, not the OS. This is how WhatsApp handled 2 million concurrent connections on a single server in 2012, when most systems would have collapsed under that load.
Supervision trees. OTP (Open Telecom Platform) is a set of design patterns and library modules built on top of Erlang. The central pattern is the supervision tree: a hierarchy of processes where supervisor processes monitor worker processes and restart them according to a defined strategy when they crash. Instead of writing defensive code to handle every failure mode, you write code that does what it should do, and let the supervisor handle failures. This is the "let it crash" philosophy — and it produces systems that are more reliable than defensive ones, not less.
Hot code loading. You can upgrade running Erlang code without stopping the process or dropping connections. This was a hard requirement for telephone switches — you cannot take a switch offline to deploy a patch. Modern distributed systems have the same requirement, and Erlang delivers it at the VM level rather than as an application-layer trick.
Distributed by default. Erlang nodes connect to each other over TCP and can send messages across nodes as if they were local. Building a distributed system in Erlang means using the same primitives as single-node code — the distribution layer is transparent. For EU backends that need to span availability zones or data centres, this is a structural advantage.
Ericsson Stockholm heritage. Joe Armstrong, Robert Virding, and Mike Williams designed Erlang at Ericsson's Erlang R&D lab in Stockholm. The language was open-sourced in 1998. Ericsson still uses it in production today — in 4G/5G base station controllers, in network management systems, in the infrastructure that handles over 40% of global mobile traffic. For EU organisations looking at supply chain provenance, Erlang has Swedish roots and has been a public open-source project for over 25 years.
Cowboy: HTTP in Pure Erlang
Cowboy is the standard HTTP server for Erlang, written by Loïc Hoguin — a French developer who has maintained it since 2011. It supports HTTP/1.1, HTTP/2, and WebSockets, and is used in production at Klarna, AdRoll, and many other high-traffic systems.
Cowboy runs as an OTP application — a supervised set of processes that manage connection acceptance, request routing, and handler execution. A complete Cowboy application looks like this:
%% src/hello_app.erl
-module(hello_app).
-behaviour(application).
-export([start/2, stop/1]).
start(_StartType, _StartArgs) ->
Routes = cowboy_router:compile([
{'_', [
{"/health", health_handler, []},
{"/api/items", items_handler, []},
{"/api/items/:id", item_handler, []}
]}
]),
{ok, _} = cowboy:start_clear(http_listener,
[{port, 8080}],
#{env => #{dispatch => Routes}}
),
hello_sup:start_link().
stop(_State) ->
cowboy:stop_listener(http_listener).
%% src/health_handler.erl
-module(health_handler).
-behaviour(cowboy_handler).
-export([init/2]).
init(Req0, State) ->
Body = jsx:encode(#{status => <<"ok">>, runtime => <<"erlang">>}),
Req = cowboy_req:reply(200,
#{<<"content-type">> => <<"application/json">>},
Body,
Req0
),
{ok, Req, State}.
%% src/items_handler.erl
-module(items_handler).
-behaviour(cowboy_handler).
-export([init/2]).
init(Req0, State) ->
case cowboy_req:method(Req0) of
<<"GET">> -> handle_get(Req0, State);
<<"POST">> -> handle_post(Req0, State);
_ ->
Req = cowboy_req:reply(405, Req0),
{ok, Req, State}
end.
handle_get(Req0, State) ->
{ok, Rows} = epgsql:squery(db_pool, "SELECT id, name FROM items ORDER BY created_at DESC LIMIT 100"),
Items = [#{id => Id, name => Name} || {ok, Id, Name} <- Rows],
Body = jsx:encode(Items),
Req = cowboy_req:reply(200,
#{<<"content-type">> => <<"application/json">>},
Body,
Req0
),
{ok, Req, State}.
handle_post(Req0, State) ->
{ok, Body, Req1} = cowboy_req:read_body(Req0),
#{<<"name">> := Name} = jsx:decode(Body, [return_maps]),
{ok, _, _} = epgsql:equery(db_pool,
"INSERT INTO items (name) VALUES ($1)",
[Name]
),
Req = cowboy_req:reply(201,
#{<<"content-type">> => <<"application/json">>},
jsx:encode(#{created => true}),
Req1
),
{ok, Req, State}.
The pattern is consistent throughout Erlang/OTP: modules declare a behaviour, implement the required callbacks, and the OTP framework handles the lifecycle. cowboy_handler is responsible for connection management; your module is responsible only for building a response.
rebar3 Project Structure
The standard Erlang build tool is rebar3. A typical project structure:
my-erlang-api/
├── rebar.config
├── src/
│ ├── my_erlang_api.app.src
│ ├── hello_app.erl
│ ├── hello_sup.erl
│ ├── health_handler.erl
│ └── items_handler.erl
└── config/
└── sys.config
%% rebar.config
{erl_opts, [debug_info]}.
{deps, [
{cowboy, "2.12.0"},
{jsx, "3.1.0"},
{epgsql, "4.7.1"}
]}.
{relx, [
{release, {hello, "1.0.0"}, [hello, sasl]},
{mode, prod}
]}.
{profiles, [
{prod, [{relx, [{mode, prod}]}]}
]}.
%% config/sys.config
[
{hello, [
{db_host, {environment, "DB_HOST"}},
{db_port, {environment, "DB_PORT"}},
{db_user, {environment, "DB_USER"}},
{db_pass, {environment, "DB_PASS"}},
{db_name, {environment, "DB_NAME"}}
]}
].
Running rebar3 release compiles all dependencies and produces a self-contained OTP release under _build/prod/rel/. This release includes the Erlang runtime, your application code, and all dependencies in a single directory that can be copied to any Linux host and run with ./bin/hello start.
Docker Setup for Erlang on sota.io
Erlang OTP releases are self-contained — they include their own Erlang runtime, so you do not need an Erlang installation in the production container:
# Build stage
FROM erlang:26-alpine AS builder
WORKDIR /app
# Install rebar3
RUN wget https://s3.amazonaws.com/rebar3/rebar3 && chmod +x rebar3
# Fetch and compile dependencies
COPY rebar.config rebar.lock* ./
RUN ./rebar3 get-deps
# Build release
COPY . .
RUN ./rebar3 release
# Runtime stage — minimal Alpine
FROM alpine:3.19
RUN apk add --no-cache libssl3 ncurses-libs
WORKDIR /app
# Copy the self-contained OTP release
COPY --from=builder /app/_build/prod/rel/hello ./
EXPOSE 8080
CMD ["./bin/hello", "foreground"]
The multi-stage build keeps the production image small — typically under 30 MB for a standard Cowboy application. The foreground command starts the Erlang node in the foreground, which is the correct mode for containerised deployments.
Deploying to sota.io
sota.io detects the Erlang application via the Dockerfile. No framework-specific configuration is required:
# sota.yaml (optional)
name: erlang-api
region: eu-central
resources:
memory: 256mb
cpu: 0.25
env:
- DB_HOST
- DB_USER
- DB_PASS
- DB_NAME
# Install sota CLI
curl -sSL https://sota.io/install.sh | sh
# Deploy from project root
sota deploy
# Deploy with managed PostgreSQL
sota deploy --with-postgres
sota.io runs on German infrastructure. Your Erlang OTP release and its PostgreSQL data both stay in the EU. For applications in telecoms, FinTech, healthcare, or any sector subject to GDPR, EU data residency is the default — no configuration required.
Database Connectivity with epgsql
epgsql is the standard PostgreSQL driver for Erlang. In an OTP application, the recommended pattern is to run epgsql connections under a pooler like poolboy:
%% src/hello_sup.erl
-module(hello_sup).
-behaviour(supervisor).
-export([start_link/0, init/1]).
start_link() ->
supervisor:start_link({local, ?MODULE}, ?MODULE, []).
init([]) ->
DbHost = os:getenv("DB_HOST", "localhost"),
DbUser = os:getenv("DB_USER", "postgres"),
DbPass = os:getenv("DB_PASS", ""),
DbName = os:getenv("DB_NAME", "app"),
PoolArgs = [
{name, {local, db_pool}},
{worker_module, epgsql_pool_worker},
{size, 10},
{max_overflow, 20}
],
WorkerArgs = [
{host, DbHost},
{username, DbUser},
{password, DbPass},
{database, DbName},
{ssl, true}
],
PoolSpec = poolboy:child_spec(db_pool, PoolArgs, WorkerArgs),
{ok, {{one_for_one, 10, 10}, [PoolSpec]}}.
The supervisor starts a pool of database connections. If a connection dies — network interruption, database restart, timeout — the supervisor restarts it automatically. This is the OTP supervision model applied to database connectivity: no manual reconnection logic, no circuit breakers written by hand, just a supervision tree that maintains the invariant.
OTP GenServer for State Management
Beyond HTTP handlers, Erlang's GenServer is the standard pattern for stateful processes:
%% src/counter_server.erl — example of OTP GenServer
-module(counter_server).
-behaviour(gen_server).
-export([start_link/0, increment/0, get/0]).
-export([init/1, handle_call/3, handle_cast/2]).
start_link() ->
gen_server:start_link({local, ?MODULE}, ?MODULE, [], []).
%% Public API
increment() -> gen_server:cast(?MODULE, increment).
get() -> gen_server:call(?MODULE, get).
%% Callbacks
init([]) -> {ok, 0}.
handle_cast(increment, Count) -> {noreply, Count + 1}.
handle_call(get, _From, Count) -> {reply, Count, Count}.
This GenServer manages a counter in process state. The supervision tree ensures that if it crashes (divide by zero, unexpected message, any reason), the supervisor restarts it immediately with initial state. The caller never sees the crash — the supervisor handles it transparently.
For a Cowboy HTTP application, GenServers are used for caching, session management, rate limiting, background job queues, or any state that should survive individual request lifecycles.
Performance for EU Infrastructure
Erlang's performance profile is distinct from compiled-to-native languages like Go or Rust, but well-suited for EU cloud deployments with real-time requirements:
- Concurrency: millions of lightweight processes, scheduled by the BEAM VM's scheduler
- Latency: predictable — BEAM preempts long-running processes after a fixed number of reductions, preventing head-of-line blocking
- Memory: ~300 bytes per process, independent heaps (no GC stop-the-world pauses across all processes)
- Throughput: lower peak throughput than native code, but the latency distribution is flatter under load
For EU FinTech systems where 95th percentile latency matters more than maximum throughput, and for real-time backends where connection drops are unacceptable, Erlang's scheduler model is an engineering advantage rather than a limitation.
Erlang vs Elixir for EU Backends
Elixir runs on the BEAM VM and shares OTP with Erlang. The key differences:
| Erlang | Elixir | |
|---|---|---|
| Syntax | Prolog-influenced, verbose | Ruby-inspired, cleaner |
| Ecosystem | Smaller, stable | Larger, growing |
| Interoperability | Native OTP | Full Erlang interop |
| EU heritage | Swedish (Ericsson) | Portuguese creator (José Valim) |
| Best for | Telco, protocol impl, core infra | Web APIs, Phoenix LiveView |
For teams building protocol implementations, telecom systems, or core infrastructure where Erlang's original design constraints apply, Erlang directly is the right choice. For teams building web applications, Elixir and Phoenix offer a more ergonomic path to the same BEAM guarantees. sota.io supports both.
GDPR and EU Data Residency
Erlang's origins at Ericsson place it firmly in the EU technology heritage. For EU organisations deploying Erlang backends under GDPR:
- PostgreSQL data stays on German servers
- Container logs remain within EU infrastructure
- No telemetry leaves the EU
- DPA governed by German and EU law
For Klarna-style FinTech backends, national telco operators, EU health data systems, or any application where GDPR compliance is a hard requirement, sota.io provides EU residency by default without the complexity of SCCs or adequacy assessments for US-hosted services.
Getting Started
# 1. Install rebar3
wget https://s3.amazonaws.com/rebar3/rebar3 && chmod +x rebar3 && mv rebar3 /usr/local/bin/
# 2. Create a new OTP application
rebar3 new app my_erlang_api
# 3. Add cowboy, jsx, epgsql to rebar.config
# 4. Install sota CLI and deploy
curl -sSL https://sota.io/install.sh | sh
sota deploy --with-postgres
Your Erlang OTP release will be running on EU infrastructure within minutes — a self-contained system with its own runtime, no interpreter to manage, and supervision trees that recover from failures automatically.
Erlang has powered European telecoms infrastructure for forty years. It was designed at Ericsson in Stockholm to solve problems — massive concurrency, zero-downtime upgrades, automatic failure recovery — that most languages still do not address at the runtime level. For EU teams building real-time systems, FinTech backends, or any application where reliability is a hard constraint rather than an aspirational goal, Erlang/OTP remains one of the most proven platforms available.
Start deploying Erlang to Europe on sota.io →
See Also
- Deploy Elixir/Phoenix to Europe — José Valim 🇧🇷 (built on the Erlang VM). Elixir is Erlang's most popular modern successor — same BEAM concurrency and OTP supervision trees, Ruby-influenced syntax. The go-to choice for EU teams building Phoenix LiveView web apps on Ericsson's runtime.
- Deploy LFE to Europe — Robert Virding 🇸🇪 (Ericsson, co-creator of Erlang). LFE (Lisp Flavoured Erlang) runs natively on the BEAM and brings Lisp macro power to Erlang's concurrency model — created by one of Erlang's original three inventors alongside Armstrong and Williams.
- Deploy Gleam to Europe — Louis Pilfold 🇬🇧. A statically typed language that compiles to Erlang and interoperates with OTP — bringing Hindley-Milner type safety to the BEAM. For EU teams who want Erlang's fault-tolerance with compile-time type guarantees.
- Deploy Occam to Europe — INMOS 🇬🇧 (Bristol 1983). CSP concurrency — the formal theory closest to Erlang's actor model. Both Occam and Erlang model concurrency as message-passing between isolated processes with no shared state.