2026-03-01 · 5 min read

Video Calling in One Docker Container

Self-hosted video calling shouldn't need Kubernetes. Here's how a single Docker container, a SQLite file, and WebRTC can handle everything you need.

TL;DR

Most self-hosted video solutions are over-engineered for personal use. A single Docker container with an embedded SQLite database can handle authentication, contacts, and call signaling — while WebRTC handles the actual media directly between devices.

If you search for “self-hosted video calling” right now, most of what you’ll find is built for conferences and enterprises. Multi-container deployments. External databases. XMPP servers. SIP gateways. Configuration files measured in hundreds of lines.

That’s fine for an organisation with a DevOps team. It’s wildly over-scoped for someone who just wants to call their family privately.

What the server actually does

A video calling server has a very limited job. Understanding what it does — and what it doesn’t do — explains why it doesn’t need to be complex.

Authentication. Verify that users are who they say they are. Manage sessions.

Contacts. Store mutual connections between users. When you add someone via friend code, the server records that relationship.

Call signaling. When User A calls User B, the server tells User B about the incoming call and helps the two devices establish a direct connection.

Push notifications. Tell the phone to wake up and show an incoming call. This is the most infrastructure-dependent part — it requires Apple (APNs) or Google (FCM) credentials.

That’s it. The server does not:

The actual video and audio flow directly between devices using WebRTC. The server is a matchmaker, not a relay.

The architecture

Why one container is enough

If the server’s job is authentication, contacts, signaling, and push notifications, the technical requirements are modest:

This doesn’t need Postgres. It doesn’t need Redis. It doesn’t need a message queue. A modern HTTP framework and an embedded SQLite database handle all of it.

# docker-compose.yml
services:
  farscry:
    image: ghcr.io/loosewiredev/farscry:latest
    ports:
      - "3000:3000"
    volumes:
      - farscry-data:/data
    environment:
      - DATABASE_PATH=/data/farscry.db
      - BETTER_AUTH_SECRET=your-secret-here
    restart: unless-stopped

volumes:
  farscry-data:

One container. One volume for the SQLite file. Two environment variables. That’s the entire deployment.

The WebRTC part

The heavy lifting — encoding, encrypting, and transmitting video and audio — happens entirely on the devices. WebRTC provides:

The server facilitates the initial connection (via ICE candidates and SDP offers/answers), then steps completely out of the media path.

For the rare cases where direct connection isn’t possible (restrictive corporate NATs), a TURN relay can be deployed separately. But most residential and mobile connections work fine with just STUN.

What this means for you

If you’re considering self-hosting video calling:

What we’re building

Farscry takes this architecture to its logical conclusion: one container, one SQLite file, zero external dependencies. It’s the simplest possible self-hosted video calling server, paired with native iOS and Android apps.

Currently in development — star the GitHub repo to follow along.

Learn about Farscry → ← All articles

More articles

Why Self-Hosted Video Calling Matters

Cloud video calling means trusting a third party with your conversations. Self-hosting puts you back in control — here's why that matters and what it takes.

End-to-End Encrypted Video Calls, Explained

What does end-to-end encryption actually mean for video calls? How WebRTC makes it possible, and why most video platforms don't truly offer it.

Video Calling Without a Phone Number

Why do video calling apps still require your phone number? It's not a technical requirement — it's a data collection strategy. Here's how friend codes offer a better alternative.