2026-04-02·5 min read·sota.io team

Deploy Deno 2.0 to Europe — EU Hosting for Deno and Fresh Apps

Deno 2.0 landed in October 2024 with Node.js and npm compatibility built in — removing the last major barrier for teams wanting to switch. With native TypeScript support, a built-in formatter, linter, and test runner, and a permission model that makes security an explicit choice rather than an afterthought, Deno 2.0 represents a serious production runtime for TypeScript backends.

But one question remains: where do you host it in production, and where does your users' data live?

For European developers, this is a compliance question, not just a preference. If you handle user data under GDPR — and almost every web application does — your runtime choice and your hosting choice are both subject to regulation. This guide shows how to deploy a Deno 2.0 application to European servers using sota.io.

Why Deno 2.0 for EU Backends

Deno 2.0 is gaining traction for:

All of these categories can handle personal data. A Deno API handling authentication processes session tokens. A Fresh app displays user profiles. Under GDPR Article 5, personal data must be processed with integrity — and where you store it is part of that.

The clean answer: run your Deno app in EU infrastructure. sota.io deploys on Hetzner Cloud in Germany. Your data never crosses a border.

Deploying a Deno 2.0 Application to sota.io

Deno provides official Docker images. A minimal Dockerfile for a Deno 2.0 app:

FROM denoland/deno:2.1.0

WORKDIR /app
COPY . .

# Cache dependencies at build time
RUN deno cache main.ts

EXPOSE 8000
CMD ["deno", "run", "--allow-net", "--allow-env", "--allow-read", "main.ts"]

Your app must listen on the PORT environment variable that sota.io injects:

// main.ts
const port = parseInt(Deno.env.get("PORT") ?? "8000")

Deno.serve({ port }, (req: Request): Response => {
  const url = new URL(req.url)
  if (url.pathname === "/health") {
    return new Response(JSON.stringify({ ok: true }), {
      headers: { "content-type": "application/json" },
    })
  }
  return new Response("Hello from Deno on EU infrastructure!", { status: 200 })
})

Deploy:

curl -fsSL https://sota.io/install.sh | sh
sota deploy

Your Deno app gets a live HTTPS URL at your-app.sota.io in under 60 seconds.

Option 2: deno.json configuration

For larger projects using a deno.json task runner, expose a start task that sota.io can detect automatically:

{
  "tasks": {
    "start": "deno run --allow-net --allow-env --allow-read main.ts",
    "dev": "deno run --allow-net --allow-env --allow-read --watch main.ts"
  },
  "imports": {
    "@std/http": "jsr:@std/http@^1.0.0",
    "hono": "npm:hono@^4"
  }
}

Connecting to PostgreSQL

Every sota.io project includes managed PostgreSQL 17. The DATABASE_URL environment variable is auto-injected at runtime.

Using the Deno postgres driver

import { Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts"

const pool = new Pool(Deno.env.get("DATABASE_URL")!, 3)

async function getUsers() {
  const client = await pool.connect()
  try {
    const result = await client.queryObject<{ id: number; email: string }>(
      "SELECT id, email FROM users ORDER BY created_at DESC LIMIT 10"
    )
    return result.rows
  } finally {
    client.release()
  }
}

Running migrations with Deno

For database migrations, use deno-migrate or run raw SQL at startup:

// db/migrate.ts
import { Client } from "https://deno.land/x/postgres@v0.19.3/mod.ts"

const client = new Client(Deno.env.get("DATABASE_URL")!)
await client.connect()

await client.queryArray(`
  CREATE TABLE IF NOT EXISTS users (
    id SERIAL PRIMARY KEY,
    email TEXT NOT NULL UNIQUE,
    created_at TIMESTAMPTZ NOT NULL DEFAULT NOW()
  )
`)

await client.end()

Run migrations as part of your start command:

{
  "tasks": {
    "start": "deno run --allow-net --allow-env db/migrate.ts && deno run --allow-net --allow-env main.ts"
  }
}

A Production Deno API with Hono

Hono is a lightweight, web-standards-based framework that runs on Deno, Node.js, Bun, and Cloudflare Workers. It is the idiomatic choice for Deno HTTP services:

// main.ts
import { Hono } from "npm:hono@^4"
import { Pool } from "https://deno.land/x/postgres@v0.19.3/mod.ts"

const app = new Hono()
const pool = new Pool(Deno.env.get("DATABASE_URL")!, 3)

app.get("/health", (c) => c.json({ ok: true }))

app.get("/users", async (c) => {
  const client = await pool.connect()
  try {
    const result = await client.queryObject<{ id: number; email: string }>(
      "SELECT id, email FROM users ORDER BY created_at DESC LIMIT 20"
    )
    return c.json(result.rows)
  } catch (err) {
    return c.json({ error: "Database error" }, 500)
  } finally {
    client.release()
  }
})

app.post("/users", async (c) => {
  const { email } = await c.req.json<{ email: string }>()
  const client = await pool.connect()
  try {
    await client.queryArray("INSERT INTO users (email) VALUES ($1)", [email])
    return c.json({ created: true }, 201)
  } finally {
    client.release()
  }
})

const port = parseInt(Deno.env.get("PORT") ?? "8000")
Deno.serve({ port }, app.fetch)

This runs on sota.io without modification. Deno.env.get("DATABASE_URL") receives the managed PostgreSQL connection string automatically.

sota.io vs Other Platforms for Deno

sota.ioRailwayRenderDeno Deploy
Data location🇩🇪 Germany🇺🇸 US (default)🇺🇸 US (default)🌍 Edge (global)
GDPR-compliant default✅ Yes❌ Requires region selection❌ Requires region selection⚠️ Edge = multi-region
PostgreSQL included✅ Yes✅ Yes✅ Yes❌ External only
Deno 2.0 support✅ Via Dockerfile✅ Via Dockerfile✅ Via Dockerfile✅ Native
Custom Dockerfile✅ Yes✅ Yes✅ Yes❌ No
Flat pricing✅ Yes❌ Usage-based❌ Usage-based⚠️ Free tier, then paid

Key difference: Deno Deploy is optimised for edge deployments — ultra-low latency globally, but your data lives in multiple regions simultaneously. For EU GDPR compliance, a single-region EU deployment on sota.io is the straightforward answer: you know exactly where your data is.

GDPR Checklist for Deno Applications

Deno's permission model makes it easy to reason about data access — but GDPR compliance requires more than secure code.

Data minimisation (Article 5): Only request the permissions your app needs. If your Deno process does not need file access, do not pass --allow-read. This reduces the blast radius of any security incident.

Request logging: Deno's standard console.log will log to your container's stdout. If you log request URLs or headers, you are potentially logging personal data (IP addresses, email addresses in query strings). Use structured logging and filter PII before writing logs.

Database residency: sota.io's PostgreSQL runs in Germany. Your DATABASE_URL connects to a server in the EU. No additional configuration needed.

Data Processing Agreements: sota.io's infrastructure runs on Hetzner Cloud, which provides a DPA. This covers the hosting layer of your GDPR compliance obligations.

External API calls: If your Deno app calls external APIs — OpenAI, Stripe, SendGrid — those calls transfer data outside the EU. Use --allow-net=api.openai.com to explicitly allowlist only the domains your app needs. This makes it easy to audit all outbound data flows.

Deploying a Fresh Framework App

Deno's Fresh framework uses an islands architecture — zero JavaScript by default, hydrated only where needed. It is one of the fastest TypeScript web frameworks for server-rendered applications.

Fresh apps deploy to sota.io with no additional configuration:

deno run -A https://fresh.deno.dev my-fresh-app
cd my-fresh-app
sota deploy

Fresh auto-detects the PORT environment variable and binds to it. Your Fresh app is live with HTTPS and EU data residency.

See Also