Introduction

Like many other people, I was an avid reader of reddit because it’s vibrant and eclectic communities, world news, and technology discussions. I started using Reddit in its early days, when many users migrated from Digg after a failed redesign. Compared to Digg, Reddit offered richer and deeper content. The memes were funnier, and the discussions more intellectual.

However nothing can last forever, and we’ve seen what’s now commonly referred to as the “enshittenification” of reddit. This term describes how a digital platform’s user experience progressively worsens as it prioritizes monetization and profit over user satisfaction and service quality. This has led to privacy issues, increased advertising, and even propaganda on the platform.

So what alternatives are there?

Open Source and Decentralized Alternatives

Many open source sites aim to replace not just Reddit, but all forms of centralized and proprietary social media. These platforms often use unique protocols to enable decentralized architecture, allowing users to run the software on their own servers. This helps avoid the issues that lead to enshittification. Some of these protocols include Nostr, Matrix, Scuttlebutt, and ActivityPub.

Among these alternatives, one stands out as being closest to the Reddit experience: Lemmy. Designed specifically as a Reddit alternative, Lemmy uses the ActivityPub protocol and is written in Rust. According to The Federation, Lemmy is the second most popular decentralized social media project, after Mastodon. This makes it an ideal choice for those looking to take control of their digital social life.

How to Self Host Lemmy

Note: this is simply how I chose to deploy the software, but there are many ways to achieve this with various tradeoffs.

I use Ansible and the lemmy-ansible projects to configure and deploy Lemmy on my server. lemmy-ansible is a good choice because this handles a lot of the tedious work of configuration of many components of the system very easily via templates. This way, as someone who is just trying to run the software without headaches, you only have to deal with the minimum configuration variables and commands necessary to stand-up a fully working Lemmy deployment. There are many other tools like Ansible that are good tools to use, but I use it here because of lemmy-ansible, which is a particularly good choice it’s a project maintained by the Lemmy developers themselves.

Get Started

To get started, clone the lemmy-ansbile git repo on your personal machine (i.e. not your homelab server) and cd into the repo you’ve just cloned. Start reading the README install section commands to get a peek at what we’ll need to do. I’ve essentially copied the README installation instructions to this post so that I can enrich it with more details to make the process easier.

First, checkout the latest official release of Lemmy by running:

git checkout $(git describe --tags)

Then you’ll create the directory for your configuration,

mkdir -p inventory/host_vars/<your-domain>

For me that’s:

mkdir -p inventory/host_vars/lemmy.vegan.dev

Config.hjson

This is the configuration for the Lemmy backend. Copy the sample configuration file:

cp examples/config.hjson inventory/host_vars/<your-domain>/config.hjson

For me that’s:

cp examples/config.hjson inventory/host_vars/lemmy.vegan.dev/config.hjson

After you copy over the examples/config.hjson, edit it as you wish. I’m sharing my filled out version here below so you can get a feel of what a finalized config will look like.

Click to expand config.hjon
{
  # for more info about the config, check out the documentation
  # https://join-lemmy.org/docs/en/administration/configuration.html

  database: {
    host: postgres
    password: "{{ postgres_password }}"
  }
  hostname: "{{ domain }}"
  pictrs: {
    url: "http://pictrs:8080/"
    api_key: "{{ postgres_password }}"
  }
  email: {
    smtp_server: "postfix:25"
    smtp_from_address: "noreply@{{ domain }}"
    tls_type: "none"
  }
  setup: {
    # Username for the admin user
    admin_username: "<my-secret-admin-username>"
    # Password for the admin user. It must be at least 10 characters.
    admin_password: "<my-secret-admin-password>"
    # Name of the site (can be changed later)
    site_name: "Wavy"
    # Email for the admin user (optional, can be omitted and set later through the website)
    admin_email: "<my-secret-admin-email>"
  }
}

Inventory Hosts

Inventory hosts defines the hosts and groups of hosts upon which commands, modules, and tasks in the Ansible playbook operate.

Copy the sample inventory hosts file:

cp examples/hosts inventory/hosts

Click to see my inventory hosts file
[lemmy]
# to get started, copy this file to `inventory` and adjust the values below.
# - `myuser@example.com`: replace with the destination you use to connect to your server via ssh
# - `ansible_user=root`: replace `root` with your the username you use to connect to your ssh server
# - `domain=example.com`: replace `example.com` with your lemmy domain
# - `letsencrypt_contact_email=your@email.com` replace `your@email.com` with your email address,
#                                              to get notifications if your ssl cert expires
# - `lemmy_base_dir=/srv/lemmy`: the location on the server where lemmy can be installed, can be any folder
#                                if you are upgrading from a previous version, set this to `/lemmy`
# - `lemmy_version`: <Optional> The back end version.
# - `lemmy_ui_version`: <Optional> overrides the front end version.
# - `pictrs_safety`: <Optional> If true, a docker container for pictrs-safety will be deployed and pict-rs will be configured to validate images through it. You will also need to set up a fedi-safety worker to validate the images.
tom@homelab-server  ansible_user=tom domain=lemmy.vegan.dev  letsencrypt_contact_email=<my-secret-email> lemmy_base_dir=/srv/lemmy pictrs_safety=false

[all:vars]
ansible_connection=ssh
lemmy_base_dir=/lemmy

Postgres

For the postgres configuration, start by copying the example file:

cp examples/customPostgresql.conf inventory/host_vars/<your-domain>/customPostgresql.conf

For me that’s:

cp examples/customPostgresql.conf inventory/host_vars/lemmy.vegan.dev/customPostgresql.conf

You can then use the PGTune Tool to get the best values for your system if you wish.

Ansible Inventory Vars

There isn’t much to actually customize here, as much of the values are templated by Ansible. Start by copying the sample vars.yml file:

cp examples/vars.yml inventory/host_vars/<your-domain>/vars.yml

For me that’s:

cp examples/vars.yml inventory/host_vars/lemmy.vegan.dev/vars.yml

Despite there not being much customization required here, here is my working example for completeness:

Click to see Ansible inventory vars
postgres_password: "{{ lookup('password', 'inventory/host_vars/{{ domain }}/passwords/postgres.psk chars=ascii_letters,digits') }}" # noqa yaml[line-length]:w

# Next two only relevant if pictrs_safety == True
pictrs_safety_worker_auth: "{{ lookup('password', 'inventory/host_vars/{{ domain }}/passwords/pictrs_safety_worker_auth.psk chars=ascii_letters,digits length=15') }}" # noqa yaml[line-length]
pictrs_safety_secret: "{{ lookup('password', 'inventory/host_vars/{{ domain }}/passwords/pictrs_safety_secret.psk chars=ascii_letters,digits length=80') }}" # noqa yaml[line-length]

# You can set any pict-rs environmental variables here. They will populate the templates/docker-compose.yml file.
# https://git.asonix.dog/asonix/pict-rs
pictrs_env_vars:
  - PICTRS__SERVER__API_KEY: "{{ postgres_password }}"
  - PICTRS__MEDIA__ANIMATION__MAX_WIDTH: 256
  - PICTRS__MEDIA__ANIMATION__MAX_HEIGHT: 256
  - PICTRS__MEDIA__ANIMATION__MAX_AREA: 65536
  - PICTRS__MEDIA__ANIMATION__MAX_FRAME_COUNT: 400
  - PICTRS__MEDIA__VIDEO__ENABLE: "True"
  - PICTRS__MEDIA__VIDEO__MAX_FILE_SIZE: 20
  - PICTRS_OPENTELEMETRY_URL: http://otel:4137
  - RUST_LOG: info
  - RUST_BACKTRACE: full

postgres_env_vars:
  - POSTGRES_USER: lemmy
  - POSTGRES_PASSWORD: "{{ postgres_password }}"
  - POSTGRES_DB: lemmy

lemmy_env_vars:
  - RUST_LOG: warn

lemmyui_env_vars:
  - LEMMY_UI_LEMMY_INTERNAL_HOST: lemmy:8536
  - LEMMY_UI_LEMMY_EXTERNAL_HOST: "{{ domain }}"
  - LEMMY_UI_HTTPS: true

postfix_env_vars:
  - POSTFIX_myhostname: "{{ domain }}"

pictrs_safety_env_vars:
  # Use this in your fedi-safety to allow your worker to authenticate to pictrs-safety
  - FEDIVERSE_SAFETY_WORKER_AUTH: "{{ pictrs_safety_worker_auth }}"
  - FEDIVERSE_SAFETY_IMGDIR: "/tmp/images"
  - USE_SQLITE: 1
  - secret_key: "{{ pictrs_safety_secret }}"
  - SCAN_BYPASS_THRESHOLD: 10
  - MISSING_WORKER_THRESHOLD: 5

Run the Playbook

The last step is to run the playbook, running the command on your personal machine (not the home lab server):

ansible-playbook -i inventory/hosts lemmy.yml --become --ask-become-pass

By default, the nginx component will be listening on port 80 for HTTP traffic and 443 for HTTPS traffic. I decided to bind nginx to port 8480 and 8488 instead because I want to run multiple self-hosted services on my home lab server and have a reverse proxy handle routing my traffic to the various services.

So, now that you’ve ran the playbook, you’ll be able to go to http://<your server ip> (or http://<your server ip>:<your port> if you customized the nginx port like I did), and you’ll be able to access your own Lemmy instance! Congratulations!

Accessing Lemmy from the Public Internet

You might, however, be wondering about how can I access this service when I’m not at home, on the same network as my home lab server? Well, as with anything with software, there’s multiple solutions to this, each can be valid for their own goals and use-cases. However, it’s actually a surprisingly complex topic, so I’ve covered this in a separate post: Ingress

There is one lemmy specific thing you’ll want to do here, and that is add a DNS A Record that points lemmy.<your-domain>.<your-TLD> at the public IP address of the VPS running the reverse proxy.