Self-hosting mail the hard way (Postfix + Dovecot + PostfixAdmin + Roundcube, and zero Mailcow)

If you came here scared of mail servers: good. That means you’re paying attention. This post is for you. I assume you can SSH into a Linux box and copy-paste carefully. I do not assume you already know what an MX record is, why port 587 exists, or why Cloudflare’s orange cloud and SMTP should never meet at a party. I wanted real email on my own machine — mail.alipourimjourneys.ir — without buying a “smart” appliance and without installing one of those giant all-in-one stacks. Mailcow is excellent. Stalwart is excellent. I still said no. Sometimes the point is to feel each moving part with your own hands, even if that means spending an evening arguing with nginx about whether a certificate belongs to mail or to Matrix. (Spoiler: when in doubt on this box, it was Matrix.) ...

July 24, 2026 · 17 min · Iman Alipour
HedgeDoc collaborative editing

Self-Hosting HedgeDoc with Docker + Nginx + Let's Encrypt

HedgeDoc is an open-source collaborative Markdown editor. Think Google Docs for Markdown: multiple people can edit the same note in real-time, with support for diagrams, math, polls, and slide decks. In this post we’ll walk through setting up your own instance on a server, secured with HTTPS. Prerequisites A Linux server with Docker and Docker Compose A domain name pointing to your server (e.g. notes.alipourimjourneys.ir) Nginx installed for reverse proxying Certbot for Let’s Encrypt certificates 1. Create the project directory mkdir ~/hedgedoc && cd ~/hedgedoc 2. Create .env POSTGRES_PASSWORD=ChangeThisStrongPassword HD_DOMAIN=notes.alipourimjourneys.ir Generate a strong password with: ...

August 29, 2025 · 2 min · Iman Alipour
Matrix, Signal but distributed

Self-hosting Matrix + Element Call with LiveKit: from zero to working (and the [not so!!] fun debugging along the way)

TL;DR: The call kept saying “waiting for media” because the browser never opened a WebSocket to LiveKit. The root cause was duplicate Access-Control-Allow-Origin headers on /sfu/get (CORS), which stopped the JWT response. Fixing CORS and ensuring the WS proxy worked (HTTP 101 in logs) solved it. What I’m building A Matrix homeserver (Synapse) at matrix.example.com (replace with your domain). TURN/STUN (coTURN) for NAT traversal. Element Call backed by LiveKit, fronted by rtc.example.com. Nginx (host) as the single reverse proxy for everything. Cloudflare DNS (with rtc.* set to DNS-only, no orange cloud). UFW firewall opened for Matrix federation, TURN, and LiveKit media ports. I used Docker for Synapse, PostgreSQL, LiveKit and the JWT helper. I used host Nginx (not Nginx in Docker) to avoid port binding conflicts on 80/443/8448. ...

August 26, 2025 · 8 min · Iman Alipour
RSS Visitors this week: