Introduction

Like many other people, I was an avid reader of reddit because it’s vibrant and eclectic communities, world news, and discussion about technology. I had originally started reading the site in it’s early days when a large number of users were migrating from the website called Digg due to a failed redesign of their site. In comparison, reddit was so much richer and deeper. Not only were the memes a lot funnier but the general discussion was a lot more intellectual. Things stayed this way for a very long time and I greatly enjoyed being a part of the community for so long.

However nothing can last forever, and we’ve seen what’s now commonly referred to as the “enshittenification” of reddit. The term “enshittification” refers to the process by which a digital platform’s user experience progressively worsens as the platform prioritizes monetization and profit over user satisfaction and quality of service. Some consequences of this include privacy issues, advertising, and lately, downright propaganda proliferating the platform.

So what alternatives are there?

Open Source and Decentralized Alternatives

There are a lot of different open source sites that aim to replace not just reddit, but all forms of centralized and proprietary social media that we consume. Many of these were designed with their own special protocols to enable a decentralized architecture, where the users can run the software on their own server, which helps avoid the reward functions which lead to enshittification in the first place. Some of these protocols include Nostr, Matrix, Scuttlebutt, and ActivityPub.

Out of this space, the one software that’s close enough to the experience of reddit, and in fact was designed specifically as a reddit alternative, is Lemmy. It uses the ActivityPub protocol and is also written in Rust! According to The Federation, Lemmy is also the second most popular decentralized social media project, second only to Mastodon. That fits the bill of what I’m looking for exactly, so let’s deploy this on our own server so that we can become self-reliant in our digital-social-life.

Self Hosting

Home Lab

I would highly recommend that anyone who wants to get into self-hosting their own software, also do it on their own hardware. Without the ability and independence to be self-reliant not just at the software level but also the physical hardware, then there are fundamental misalignments that prevent true freedom in your digital life.

The homelab server that I currently use is the HP Elite Mini 800 G5.

It’s a decent server that can be had refurbished for just $150-$250 dollars, depending on configuration.

Lemmy Software

Note that this is how I want to run and deploy my software, because it’s simple and easy, and that’s all I’m looking for. There are however, a number of different ways you can run your self-hosted services, and some of them might be much better and robust systems than mine. However, this works for me and I’m sharing it to help others that want to follow along and who don’t have the know-how to do this on their own.

I use Ansible and the lemmy-ansible projects to configure and deploy Lemmy on my server. I think 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.

To get started, clone the lemmy-ansbile git repo on your personal machine (i.e. not your homelab server). Start reading the README install section commands (I’ve used a permalink to guarantee the follow steps match my description).

First you’ll create the directory for your configuration, for me that’s:

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

Config.hjson

The next step in the lemmy-ansible instructions are to copy the example config.hjson file. This is the configuration for the Lemmy backend.

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. I’m intentionally not adding anything here beyond showing you a what the lemmy-ansible example files look like after being filled in.

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

Next you’ll copy the example inventory hosts file. This defines the hosts and groups of hosts upon which commands, modules, and tasks in the Ansible playbook operate.

Click to see 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, just use the PGTune Tool to get the best values for your system, as recommended by the lemmy-ansible README.

Ansible Inventory Vars

There isn’t much to actually customize here, as much of the values are templated by Ansible. Still, for completeness, here is my working example:

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.

Ingress

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! 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 will cover how to do this in a separate blog post.