Self-Hosted Discord Alternative: Haven

Apr 2, 2026

While I'm not as much of a gamer as I once was, I still manage to find the odd evening for a game, and it's typically handy to have a voice chat option when gaming with friends. While a few more modern games (and now Steam) have voice chat built into them, I generally prefer having an independent solution for this and you won't find me playing modern (or even recent, titles).

I've used a variety of things for this in the past, including Ventrilo, the until-recently home of this in Discord, and I'm currently hosting Teamspeak. In the case of Discord (which I've ceased using), I tend not to use 90% of it's 'features' (many of which are to me more in the realms of social media) and instead I'm simply after some basic chat (+GIFs) and voice chat capability, screen sharing is a bonus. With Discord (among other things) moving to implement age verification, many others also seem to be intending to leave; this serves as a bit of a template for one option: host a server for your friends/group/community/family.

Looking for Better Alternatives

The first alternative I've looked at is Teamspeak, a free (but not open source) voice chat application, which as noted above I'm currently hosting on my Proxmox server. In general, this works fine for my typical use case over a local connection (playing a game with my partner, who's in another room). However, in order to invite other friends to this server, I'd need to open respective ports on my router in order for them to access the server (and probably would need a static IP). With a hosted virtual private server (VPS) this is probably something I'd go for (there are providers which specifically host Teamspeak servers if that's what you're after).

The second option I looked at for Teamspeak is using a kind of tunnel service (first Tailscale funnel and secondly Cloudflare tunnel; I use Tailscale already and have at least one domain managed in Cloudflare). However - this quickly runs into a roadblock which wasn't initially apparent (to my uninitiated self), in that Teamspeak communicates over UDP, TCP and HTTP - tunelling generally only works for HTTP/HTTPS traffic (at least without other work/conversion) - so that plan was dead in the water.

Mumble is another open source (one-up on Teamspeak) and fairly minimal text + voice chat option here, but similarly to Teamspeak it also uses UDP so doesn't fit the requirements for use via a tunnelling service.

So, looking for a voice chat application I could self host which communicates over HTTP/HTTPS, I found Haven. Generally it has the look and feel of a slightly less flashy version of Discord (this is a plus) while also retaining most of the functionality (and 100% of what I need from it). As a client, the application is accessible directly in your browser, but as a bonus for those who want it has both desktop and Android client applications.

Haven: Getting Started

Haven is MIT-licensed on GitHub, and the server component has multiple documented installation methods for Linux systems, including the preferred method via Docker compose (this is arguably easier to update), as well as a running a server directly on a host.

In terms of the networking setup to share your server with friends, Haven has multiple options. The first two don't require any manual intervention on your part, and will work with either installation method:

  • Use it locally, with IP addresses within your home network - works for who you live with and maybe your neighbour which has your WiFi password
  • Use the built-in Cloudflare or Localtunnel options - these will give you (likely randomly generated) externally accessible URLs which 'just work'. If your server is intended to be ephemeral, or you're happy to ship around a URL to friends whenever you want to jump into a game - this might work pretty well for you.

Beyond this, you can also set up your own persistent tunnel (with Cloudflare, Localtunnel or even Tailscale funnel; the latter is arguably less straightforward to setup). We'll look at using a persistent tunnel below, and link it to the domain which this blog is hosted on.

Installation

In this instance I'm going to start with a Debian 12 LXC image, running on Proxmox. We can quickly update it after starting:

apt update && apt upgrade -y

Note: I don't use sudo here as I'm running as root for the majority of important tasks within the container, but on your device or in your container you might need it.

In either scenario, we'll use the git repository as a starting point:

git clone https://github.com/ancsemi/Haven.git
cd Haven

Option 1: Docker

Haven hosts a docker-compose.yml in their repository, and a pre-built image is available to save building the repository from source; this image is used by default.

To get setup with Docker, we can first install the Docker engine (if you don't already have Docker in your environment):

Docker Installation

We'll follow the Docker instructions on doing this in a Debian-based system using apt but I've added the (March 2026) commands for doing this below.

The first step is to remove any existing Docker components:

apt remove $(dpkg --get-selections docker.io docker-compose docker-doc podman-docker containerd runc | cut -f1)

After that we add Docker's GPG key:

apt install curl -y
install -m 0755 -d /etc/apt/keyrings
curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc
chmod a+r /etc/apt/keyrings/docker.asc

And from here modify the apt sources to add the Docker repository:

tee /etc/apt/sources.list.d/docker.sources <<EOF
Types: deb
URIs: https://download.docker.com/linux/debian
Suites: $(. /etc/os-release && echo "$VERSION_CODENAME")
Components: stable
Architectures: $(dpkg --print-architecture)
Signed-By: /etc/apt/keyrings/docker.asc
EOF

Now we can install the Docker packages:

apt update
apt install docker-ce docker-ce-cli containerd.io docker-buildx-plugin docker-compose-plugin -y

And check the status of Docker with (Ctrl+C /:q to exit):

systemctl status docker

From here, assuming we're in the Haven repository, we can use Docker compose to pull the images and start the container in detached mode:

docker compose up -d

I've added the default YAML compose file below for reference, if you're curious:

docker-compose.yml
# ── Haven Docker Compose ─────────────────────────────────
# Quick start:  docker compose up -d
# Stop:         docker compose down
# View logs:    docker compose logs -f haven
# Update:       docker compose pull && docker compose up -d --force-recreate
# Shell:        docker compose exec haven sh
# ─────────────────────────────────────────────────────────

services:
  haven:
    image: ghcr.io/ancsemi/haven:latest
    # build: .                    # Uncomment to build from source instead of using the pre-built image
    container_name: haven
    ports:
      - "${PORT:-3000}:${PORT:-3000}" # Main HTTPS port
      - "${REDIRECT_PORT:-3001}:${REDIRECT_PORT:-3001}" # HTTP → HTTPS redirect
    volumes:
      - haven_data:/data
      # ── Or use a local folder instead (good for NAS / Synology): ──
      # - ./haven-data:/data
    environment:
      - PORT=${PORT:-3000}
      - HOST=0.0.0.0
      # - SERVER_NAME=My Haven           # Name shown in the UI
      # - ADMIN_USERNAME=admin            # First account to register becomes admin
      # - GIPHY_API_KEY=                  # Optional: enable GIF search
      # - VAPID_EMAIL=mailto:you@example  # Optional: email for push notifications
      # - TURN_URL=turn:your-server:3478  # Optional: TURN relay for voice over internet
      # - TURN_SECRET=your-secret         # Shared secret (coturn --use-auth-secret)
    # Uncomment to load settings from a .env file:
    # env_file: .env
    restart: unless-stopped

volumes:
  haven_data:

After this completes, your service should now be running on https://<LOCAL_IP_ADDRESS>:3000. To check the IP address of your machine, you can use the following to list all of your network interfaces:

ip a

If you know you have e.g. an ethernet interface on eth0, you can use:

ip a show eth0

You can take the address (an IPv4 address will look something like 192.168.0.127) you can visit e.g. https://192.168.0.127:3000 to access your Haven server; note you'll probably have a 'Be careful. Something doesn’t look right.' kind of warning message the first time you access this, you can click 'Advanced' -> 'Proceed'. You should now see a login/register page for Haven. You can register with the username admin in order to have access to server admin functions (you can change the display name later). If you don't need a persistent tunnel for external access, you're now done, congrats! Have a look through setting up your server, and if you're going to use the built-in tunnel functionality, at least consider whitelisting the usernames who are able to register.

Updating

In terms of updating, it's typically going to look like:

docker compose pull
docker compose up -d --force-recreate

Option 2: Server Directly on the Host

If you're running this in a Linux Container (LXC), you already have one level of isolation on your host, and you don't necessarily need to use Docker. Similarly, if this e.g. the only thing running on a small machine, you could install it directly on the host.

To use the server, the main thing we'll need is

apt install nodejs npm

To run the server, from the repository we've cloned above, we can use the start scripts provided:

chmod +x start.sh && ./start.sh

This will run until killed, so any other things we want running will need to run another way.

Tunnel Setup

Once this is up and looks OK locally, we can add the route in Cloudflare by configuring your tunnel in the ... menu, and then clicking Add Route in the Routes diagram. You'll need to select your subdomain (I used hvn), and add the service URL (Haven uses https://localhost:3000).

One of the key things here which otherwise tripped me up was that this needed a configuration change in Cloudflare Zero Trust (you might need to make an Org to get in; the basics of this are still free-tier) to turn off TLS verification . For Docker, I'd tried using --no-tls-verify in the run command and NO_TLS_VERIFY=true in the environment but neither helped, and I was relegated to the Cloudflare dashboard.

You'll need to select your tunnel in the page linked above, select the 'published application routes' tab, and then click on the '⋮' menu, select 'Edit', go down to 'Additional application settings', select 'TLS' and turn on 'No TLS Verify'. Save this, and with luck you can access your server via your subdomain added earlier (e.g. https://hvn.fluids.rocks).

Modifications: Cloudflare Tunnel in Docker

If you want to use Docker but want a peristent tunnel URL, we can modify the docker-compose.yml file to add a cloudflared service which provides the tunnel to the networks. This requires:

  1. A Cloudflare account
  2. A domain managed by Cloudflare which you can attach this to (i.e., the persistent part)

Before we do anything else, we can quickly take down our Docker services:

docker compose down

Before any of that, we will need to create a tunnel on Cloudflare, and get a tunnel token. Once you've created your tunnel, you can extract the token from one of the installation options which Cloudflare gives you (copy paste and take the long text token at the end). We'll insert that in place of <TUNNEL_TOKEN> in the below YAML file:

docker-compose-cloudflared.yml
# ── Haven Docker Compose ─────────────────────────────────
# Quick start:  docker compose up -f docker-compose-cloudflared.yml -d
# Stop:         docker compose down
# View logs:    docker compose logs -f haven
# Update:       docker compose -f docker-compose-cloudflared.yml pull && docker compose up -f docker-compose-cloudflared.yml -d --force-recreate
# Shell:        docker compose exec haven sh
# ─────────────────────────────────────────────────────────

services:
  cloudflared:
    image: cloudflare/cloudflared:latest
    container_name: cloudflared
    restart: unless-stopped
    command: tunnel --no-autoupdate run
    environment:
      - TUNNEL_TOKEN=<TUNNEL_TOKEN>
    ports:
      - "${PORT:-3000}:${PORT:-3000}" # Main HTTPS port
      - "${REDIRECT_PORT:-3001}:${REDIRECT_PORT:-3001}" # HTTP → HTTPS redirect
    networks:
      - proxy
  haven:
    image: ghcr.io/ancsemi/haven:latest
    # build: .                    # Uncomment to build from source instead of using the pre-built image
    container_name: haven
    #ports:
    #  - "${PORT:-3000}:${PORT:-3000}"    # Main HTTPS port
    #  - "${REDIRECT_PORT:-3001}:${REDIRECT_PORT:-3001}"  # HTTP → HTTPS redirect
    volumes:
      - haven_data:/data
      # ── Or use a local folder instead (good for NAS / Synology): ──
      # - ./haven-data:/data
    environment:
      - PORT=${PORT:-3000}
      - HOST=0.0.0.0
      # - FORCE_HTTP=true
      # - SERVER_NAME=My Haven           # Name shown in the UI
      # - ADMIN_USERNAME=admin            # First account to register becomes admin
      # - GIPHY_API_KEY=                  # Optional: enable GIF search
      # - VAPID_EMAIL=mailto:you@example  # Optional: email for push notifications
      # - TURN_URL=turn:your-server:3478  # Optional: TURN relay for voice over internet
      # - TURN_SECRET=your-secret         # Shared secret (coturn --use-auth-secret)
    # Uncomment to load settings from a .env file:
    # env_file: .env
    restart: unless-stopped
    network_mode: service:cloudflared
volumes:
  haven_data:

networks:
  proxy:
    name: proxy

In this file we've added a separate network configuration proxy; for the cloudflared service we've mapped the ports 3000 and 3001 (used by Haven) and in the haven service used network_mode: service:cloudflared. The port mapping allows local IP access (if you don't need it, you can remove this), and the network_mode: service:cloudflared ensures that Haven can use our tunnel. Note that we're not mapping ports in the haven service here (the port mapping has just moved up).

We can copy the contents of the file above, and paste it to a file called docker-compose-cloudflared.yml:

nano docker-compose-cloudflared.yml

From here, we can start up our modified services:

docker compose -f docker-compose-cloudflared.yml up -d --force-recreate

Before we go any further, if you've kept the port mapping it's best to check that you can access your service at your local IP address (e.g. https://192.168.0.127:3000, but using the IP address of your machine). If there's an issue already here, we might not succeed in the tunnel bit.

Modifications: Cloudflare Tunnel on the Host

To use cloudflared with an installation of Haven on the host, we can pull down the binary from GitHub:

curl -L https://github.com/cloudflare/cloudflared/releases/latest/download/cloudflared-linux-amd64 -o /usr/local/bin/cloudflared
chmod +x /usr/local/bin/cloudflared

We can verify this is installed:

cloudflared --version

You can run the binary itself to create a tunnel (e.g. without a token this would create an anonymous/ randomized url), but we'll set it up as a service using our tunnel token:

cloudflared service install <TUNNEL_TOKEN>

Once installed, we can start the cloudflared service:

systemctl start cloudflared

If any of the configuration (IP addresses, the TLS settings etc) changes, it's best to restart the service:

systemctl restart cloudflared

From here, we would then run the server:

chmod +x start.sh && ./start.sh
https://fluids.rocks/posts/rss.xml