Preamble
So… I don’t doubt many readers have heard of the huge fiasco between Reddit and its users. Actually, there have been many recent fiascos between large companies and their users. (Like just recently, YouTube and Invidious. Invidious was the only way for me to watch videos on YouTube…) As a result, many people have started to consider the Fediverse more and more: a decentralized way of social networking where users are in charge. (And usually, through using FOSS!)
I’ve been eying the Fediverse myself for some time, but have been putting it off for a while, telling myself it wasn’t a priority because I only use two social media services (Reddit included). Well, Reddit’s gone, so I guess it’s time to federate!
I also am in a bit of a pickle for another reason: I don’t really trust others with my data anymore (no offense to anyone running a server). Plus, ideally with the Fediverse, everyone would be self-hosting their account so that they don’t overload existing servers (like lemmy.ml has seen) and are in more control of their data.
So I decided to host my own private Lemmy instance! Unfortunately, the current documentation is only for Docker/Ansible on amd64 systems, and since I have a Raspberry Pi with Podman, that doesn’t entirely work out. So after some tinkering, I’ve found a way to self-host Lemmy on Podman, with just a few console commands!
Lastly, I should state two things you’ll need: (1) Podman 4.0 or higher, which meant for me unofficially upgrading my Raspbian install to Debian 12, and (2) a bit of technical expertise with Linux. (I mean, you are self-hosting, after all, so you probably know more than me.)
I should also state that I initially this while very hyper off the fact that I finally did this, so I might not explain things or go on tangents. I’m really sorry if I do do that! If you notice/encounter any errors or have suggestions, list them in the comments below and I’ll update this article! I also recommend you have a look at a guide that explains what you’re doing in more detail first, like this one. Just keep in mind that it’s not updated for the latest version of Lemmy/using Podman so it might not work.
Ready?
LET’S DO THIS
First off, this tutorial is based off the Docker files from version 0.17 of Lemmy. I recommend you have a look at it here: https://github.com/LemmyNet/lemmy/tree/release/v0.17/docker/prod. If you want to run later versions, you just need to change the images of the containers you will be creating (more on that later). Make sure to look to the release branch of the version you want (make sure it says “release” in the URL just like the link I provided). Look for where it says “image” for each container, and then just copy the release tag for each container to the docker.io
links in each command.
You could create a Podman network for this, but those haven’t actually worked in testing. We’re going to be using the podman pod
commands, which also help to organize our files.
1. Prepare the files!
Make sure you’re in your home directory. You’ll need to create a working folder to put all your Lemmy data in, so we’ll make a folder in the home directory where we store configuration files and volumes. Let’s do that now:
mkdir -p ~/Lemmy cd ~/Lemmy # Create folders for pictrs (which will store our picture data) and postgres mkdir -p volumes/pictrs mkdir -p volumes/postgres
We also need to assign special permissions to one of our volumes, so that pictrs can use it.
sudo chown -R 991:991 volumes/pictrs sudo chmod a+rw volumes/pictrs
Next, we’ll need to copy over the Nginx config from Lemmy’s Docker tutorial:
worker_processes 1; events { worker_connections 1024; } http { upstream lemmy { # this needs to map to the lemmy (server) docker service hostname server "lemmy:8536"; } upstream lemmy-ui { # this needs to map to the lemmy-ui docker service hostname server "lemmy-ui:1234"; } server { # this is the port inside docker, not the public one yet listen 80; # change if needed, this is facing the public web server_name localhost; server_tokens off; gzip on; gzip_types text/css application/javascript image/svg+xml; gzip_vary on; # Upload limit, relevant for pictrs client_max_body_size 20M; add_header X-Frame-Options SAMEORIGIN; add_header X-Content-Type-Options nosniff; add_header X-XSS-Protection "1; mode=block"; # frontend general requests location / { # distinguish between ui requests and backend # don't change lemmy-ui or lemmy here, they refer to the upstream definitions on top set $proxpass "http://lemmy-ui"; if ($http_accept ~ "^application/.*$") { set $proxpass "http://lemmy"; } if ($request_method = POST) { set $proxpass "http://lemmy"; } proxy_pass $proxpass; rewrite ^(.+)/+$ $1 permanent; # Send actual client IP upstream proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } # backend location ~ ^/(api|pictrs|feeds|nodeinfo|.well-known) { proxy_pass "http://lemmy"; # proxy common stuff proxy_http_version 1.1; proxy_set_header Upgrade $http_upgrade; proxy_set_header Connection "upgrade"; # Send actual client IP upstream proxy_set_header X-Real-IP $remote_addr; proxy_set_header Host $host; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; } } }
Save this file as ~/Lemmy/nginx.conf
.
Now, we’ll need to come up with a few things.

First, generate a password for your database and API key. I’ll henceforth refer to these as $DB_PASSWORD
and $API_PASSWORD
for the rest of this guide. Done? So here’s the next (and last) file you’ll have to make:
Download lemmy.hjson
from the GitHub repository I linked earlier. Or just copy it from here. In either case, you’ll need to replace the lines I highlighted with the PASSWORD
variables. Don’t worry about changing anything else since we will be able to do that with the UI once we set up Lemmy.
{ # for more info about the config, check out the documentation # https://join-lemmy.org/docs/en/administration/configuration.html # only few config options are covered in this example config # the domain name of your instance (eg "lemmy.ml") hostname: "localhost" # address where lemmy should listen for incoming requests bind: "0.0.0.0" # port where lemmy should listen for incoming requests port: 8536 # Whether the site is available over TLS. Needs to be true for federation to work. tls_enabled: true # pictrs host pictrs: { url: "http://pictrs:8080/" api_key: "$API_PASSWORD" } # settings related to the postgresql database database: { # name of the postgres database for lemmy database: "lemmy" # username to connect to postgres -- not the same as admin_username! user: "lemmy" # password to connect to postgres password: "$DB_PASSWORD" # host where postgres is running host: "postgres" # port where postgres can be accessed port: 5432 # maximum number of active sql connections pool_size: 5 } }
3. LET’S COPY SOME COMMANDS
Note:
If you’re using an ARM processor, you need to install
qemu-user-static
.Starting with 0.18.0, Lemmy builds on Docker are no longer for arm64 devices, meaning you have to emulate it with QEMU. Luckily, Lemmy isn’t too taxing on hardware so you shouldn’t notice any dips in performance.
Step 1 is to create a Podman pod. This will allow our containers to talk with one another with only two ports facing the outside world. (You only need port 8537, but we’ll open 8536 in case we need it.)
podman pod create --publish 8536:8536 --publish 8537:80 lemmy-pod
Next, we need to create an Nginx container. This is ultimately how every other container is going to get its port forwarded to the outside world.
podman run \ --detach \ -v $HOME/Lemmy/nginx.conf:/etc/nginx/nginx.conf:Z \ --restart on-failure \ --name lemmy-proxy \ --pod lemmy-pod \ docker.io/library/nginx:1-alpine
Next step is to create a Lemmy container.
podman run \ --detach \ --restart on-failure \ --name lemmy \ --pod lemmy-pod \ --env RUST_LOG="warn,lemmy_server=debug,lemmy_api=debug,lemmy_api_common=debug,lemmy_api_crud=debug,lemmy_apub=debug,lemmy_db_schema=debug,lemmy_db_views=debug,lemmy_db_views_actor=debug,lemmy_db_views_moderator=debug,lemmy_routes=debug,lemmy_utils=debug,lemmy_websocket=debug" \ --env RUST_BACKTRACE=full \ -v $HOME/Lemmy/lemmy.hjson:/config/config.hjson:Z \ --arch amd64 \ docker.io/dessalines/lemmy:0.18.0
Then we’ll need to create another container for the UI. This is how you will be able to access Lemmy from your browser.
podman run \ --detach \ --restart on-failure \ --name lemmy-ui \ --pod lemmy-pod \ --env LEMMY_UI_LEMMY_INTERNAL_HOST=lemmy:8536 \ --env LEMMY_UI_LEMMY_EXTERNAL_HOST=localhost:1236 \ --env LEMMY_HTTPS=true \ --env RUST_BACKTRACE=full \ --arch amd64 \ docker.io/dessalines/lemmy-ui:0.18.0
Then, we need to create a pictrs server. This is how Lemmy will be able to store and retrieve pictures you want to upload to your server.
podman run \ --detach \ -v $HOME/Lemmy/volumes/pictrs:/mnt \ --security-opt label=disable \ --restart on-failure \ --name pictrs \ --env PICTRS_OPENTELEMETRY_URL=http://otel:4137 \ --env PICTRS__API_KEY="$API_KEY" \ --env RUST_LOG=debug \ --env RUST_BACKTRACE=full \ --user 991:991 \ --pod lemmy-pod \ docker.io/asonix/pictrs:0.4.0-beta.19
FINALLY. Our last server is Postgres, which stores a bunch of data.
podman run \ --detach \ --restart on-failure \ --name postgres \ -v $HOME/Lemmy/volumes/postgres:/var/lib/postgresql/data:Z \ --env POSTGRES_USER=lemmy \ --env POSTGRES_PASSWORD="$DB_PASSWORD" \ --env POSTGRES_DB=lemmy \ --pod lemmy-pod \ docker.io/library/postgres:15-alpine \ postgres -c session_preload_libraries=auto_explain -c auto_explain.log_min_duration=5ms -c auto_explain.log_analyze=true -c track_activity_query_size=1048576
And WE’RE DONE!! Head over to localhost:8537
to enjoy the fruits of your labour.
But…
Now here’s where I try to do rapid-fire coverage of the things I didn’t cover, or all the errors you might encounter. It may seem like light coverage, but if I covered all these things in-depth, this article would be twice the length it is now!
Why is my homepage white/how do I start seeing some posts?
This is a huge one and a problem I’ve had and thought about for quite a while after I set up my server. Turns out, you first have to go to your admin settings. Refresh the page so that Lemmy can pull all previous settings (like which domains you’re federated with). Scroll to the near-bottom and start adding instances to your federation allowlist (like lemmy.ml or lemmy.world. Or just copy the list on https://join-lemmy.org). Then, go back to the homepage, and search up a community. For instance, to get all posts for https://lemmy.ml/c/programming, search up “!prog[email protected]”. There’s also a test community at https://lemmy.ml/c/test/.
At this point, you still don’t see anything. Refresh the page after you hit the search button, and you should get a link to the community you want. Click on the link, and then hit the subscribe button. (Apparently, actions on other communities will only be recognized from your server when someone’s subscribed to it.)
Now that you’re done this, go back to your settings and remove everything from the allowlist. Here’s the reason why:
Note: 🤓 alert, you can skip past this if you want.
Lemmy works by not pulling content from servers, but actions. For instance, when someone makes a post about beans, that action is broadcast from the instance the user made the post on to any servers that (a) have the community listed on their server (which can happen when a user searches for a community) and (b) have that Lemmy instance in their allowlist or “recognized” list. When someone wants to comment on that post, or vote on it, it’s the same deal. This is also why you don’t see any posts when you search up for a community for the first time, and also why you can still see posts from communities on a long-gone instance (like vlemmy). All those posts are not just read from those other servers, but cached on your own!
So when both those lists are empty, we need to put a Lemmy community on the allowlist so we can search up for a community, and then once that community is on the server, it can start pulling actions from the Lemmy instance that community originated from (it’s on the “recognized” list too now, so we can take it off of the allowlist).
Then, we need to clear the allowlist. This tells Lemmy that if anybody comments on that post with beans, it can be anyone from any instance — but don’t worry if you get any negative comments, as the community’s moderators can always delete comments and ban users. This allows you to see posts made from people in personal instances, which are hard to all manually include with an allowlist. Every time someone comments on that post in the example, their domain is added to the recognized list. If their server has any communities you want to search up and start subscribing to, this can now be possible. So we have a bit of a snowball effect that allows us to interact with ALL THE PEOPLE, but we can still leave up a denylist for instances that are borderline illegal!
How do I back up data on my Lemmy instance?
Data isn’t stored on the containers themselves — at least, not important data. Everything you need to back up is actually in the ~/Lemmy
folder we created a while back – that’s why it has all those volumes. That’s where Postgres and pictrs store their data.
I rebooted my system, but now I can’t access my instance!
The containers are automatically destroyed when you turn off your computer. You can read up here on how to make them systemd services so they’re automatically recreated when you turn on your computer. (Note: on my PC, with Podman 4.51, they aren’t automatically destroyed? Still, I recommend making the pod a systemd service just in case.)
I have a domain I use to access my server, how do I set it up with Lemmy?
Try installing something like Apache and then making a reverse proxy from the port 8357 to 80/443.
Update: If you use Apache, it should be good to note that you also need to proxy WebSockets. Here’s an approach I adapted from this StackOverflow post:
<IfModule mod_ssl.c> <VirtualHost *:443> ProxyPreserveHost On ProxyRequests Off ServerName lemmy.example.com ServerAlias lemmy.example.com # HTML <Location /> ProxyPass http://localhost:8537/ ProxyPassReverse http://localhost:8537/ </Location> # WebSockets (location is /api because all the WebSocket calls I have seen were to it) <Location /api> ProxyPass ws://localhost:8537/api ProxyPassReverse ws://localhost:8537/api </Location> # [...] </VirtualHost> </IfModule>
And that’s all!
That’s everything you need to get a Lemmy instance on your own computer! Hope you found this useful (and not too long), and thanks for reading!
Leave a Reply