[Guide] Getting Lemmy Working on Docker

Boy howdy, there are a lot of people coming to the matrix chat trying to figure out how to get lemmy working on docker who are stuck on the official documentation. This document is my guide on how I got Lemmy working. I’ll also share what I don’t have working yet to inspire further.

Please feel free to steal anything you want from this and put it into the official docs. I don’t know the contributing policy and it sounds hard and I’m busy at the moment.

Of note: I add a nginx container in this setup so that you don’t have to do crazy hacks on your end for locations. If you already have an nginx reverse proxy that you are using, just use this one as a 2nd layer of nginx. There is low overhead, so don’t worry about it.

Setup

For this guide, I’m requiring that you already have your own reverse proxy setup in place that can handle all the SSL termination. I’m doing this because I think that most people who are setting up Lemmy for the first time on Docker aren’t setting up their first Docker container.

Because I’m requiring that you setup your own SSL termination (caddy, ACME, Nginx Proxy Manager, etc.) before you begin, I will not talk further about https, certificates, or rotation. But before I do: Don’t host a website in 2023 that doesn’t serve content securely. Make sure that you get your stuff setup, including any certificate rotation. If you don’t get this setup completed, I suggest that you shouldn’t continue or host a public website.

I also require that you be able to use docker-compose.

Get Files

Download these 3 files to your working directory from my github gist. You can download as zip or get them one at a time by scrolling down.

Prepare Working Directory

mkdir -p volumes/pictrs
sudo chown -R 991:991 volumes/pictrs

Edit Config Files

  • In the docker-compose.yml file, change the port, hostname, and database password.
  • In the lemmy.hjson file, change the admin username/password, hostname, database password, and email settings. You can take out the entire email section if you want to.
  • No changes to the nginx.conf file.

Start It Up

Now you’re ready to start the containers!

You’re pretty much good to go. Login to your lemmy instance. You should be able to use your docker host ip at your defined port OR via your reverse proxy lemmy domain host name.

docker-compose up

Watch the pretty log messages.

You should be able to curl your new admin user and get valid json back: curl -H 'Accept: application/activity+json' https://lemmy.yourdomain.net/u/yourAdminUser

Press Ctrl+C if everything is working great and start it up as docker-compose up -d to make it a persistent running setup.

Troubleshooting

If you get the default nginx start page, it means that your nginx container isn’t reading/following any nginx config file. Figure out why. Do you accidently have a blank directory created that is called nginx.conf instead of an actual file? Did you comment out the nginx.conf bind mount?

Update the Images

In order to update the image to the latest release of lemmy, you have to manually go to your docker-compose file and edit the docker image tag to the latest version number. Then, you need to bring your container back up. Steps:

  1. Edit the docker-compose.yml file image tags from 17.4 to whatever else comes out
  2. Run a docker-compose up which will update images as needed (I personally prefer to do a docker-compose down first, but that’s personal preference):
docker-compose up

Watch the pretty log messages. Press Ctrl+C if everything is working great and start it up as docker-compose up -d to make it a persistent running setup.

Limitations

I don’t know anything about docker. I’m a docker noob. Please correct me for anything that you think is a bad idea.

Why are the docker tags for lemmy and lemmy-ui “latest” for arm64/v8? Shouldn’t there be a latest-arm and a latest-x86 or something? Annoying that I have to pin my lemmy images to a specific version in docker. I would prefer to let them be set to 1 image that gets updated and have watchtower deal with updating the image on a schedule of my choosing.

Sources

I wouldn’t be here without the matrix chat, https://join-lemmy.org/docs/en/administration/install_docker.html, and this post: https://lemmy.ml/post/1127760

Reverse Proxies

There have been some suggested reverse proxy configs for Caddy and Apache!

Caddy

Thanks to @tmpod@lemmy.pt for this caddyfile:

lemmy.tld {
	header {
		# Only connect to this site via HTTPS for the two years
		Strict-Transport-Security max-age=63072000

		# Various content security headers
		Referrer-Policy same-origin
		X-Content-Type-Options nosniff
		X-Frame-Options DENY
		X-XSS-Protection "1; mode=block"
		# disable FLoC tracking
		Permissions-Policy interest-cohort=()

		# Hide Caddy
		-Server
	}

	# Enable compression for JS/CSS/HTML bundle, for improved client load times.
	# It might be nice to compress JSON, but leaving that out to protect against potential
	# compression+encryption information leak attacks like BREACH.
	@encode_mime {
		header Content-Type text/css
		header Content-Type application/javascript
		header Content-Type image/svg+xml
	}
	encode @encode_mime gzip

	request_body {
		max_size 8MB
	}

	@pictshare_regexp path_regexp pictshare_regexp \/pictshare\/(.*)
	redir @pictshare_regexp /pictrs/image/{re.pictshare_regexp.1} permanent

	# Supposedly better than having three different named matchers using standard matchers
	# ¯\_(ツ)_/¯
	@backend `
	path('/api/*', '/pictrs/*', '/feeds/*', '/nodeinfo/*', '/.well-known/*')
	|| header({'Accept': 'application/*'})
	|| method('POST')
	`
	reverse_proxy @backend lemmy:8536 {
		# This was needed because of a bug, but it probably has been fixed in the meanwhile.
		# Will have to test later.
		header_down -Transfer-Encoding
	}

	reverse_proxy lemmy-ui:1234
}

Apache

Here are a few apache configs you can draw from.

The best apache config I’ve seen so far is by DeadCade in the comments here.

<VirtualHost *:443>
        ServerName lemmy.deadca.de
        SSLEngine on
        ProxyRequests on
        ProxyPreserveHost on
        ProxyTimeout 600

        SetEnv proxy-nokeepalive 1
        SetEnv proxy-sendchunked 1

        <Location />
                Allow from all
                ProxyPass http://127.0.0.1:(INTERNAL LEMMY PORT)/
                ProxyPassReverse http://127.0.0.1:(INTERNAL LEMMY PORT)/
        </Location>

    ErrorLog "ERROR LOG LOCATION"
    CustomLog "ACCESS LOG LOCATION" common

    # Enable mod_rewrite (requires "a2enmod rewrite")
    RewriteEngine on

    # WebSockets support (requires "a2enmod rewrite proxy_wstunnel")
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/?(.*) "ws://127.0.0.1:(INTERNAL LEMMY PORT)/$1" [P,L]

SSLCertificateFile FULLCHAIN.PEM LOCATION
SSLCertificateKeyFile PRIVKEY.PEM LOCATION
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>

If you need another apache config, this was suggested by Samsonite (though, he knows that it needs cleaned up). Comment if you have suggestions for what to remove:

<VirtualHost *:80>
    ServerName mylemmydomain.com
    RewriteEngine On
    RewriteCond %{HTTPS} !=on
    RewriteCond %{HTTP_HOST} !^(localhost|internallemmyip)
    RewriteRule ^/(.*) https://%{SERVER_NAME}/$1 [R,L]


</VirtualHost>

<IfModule mod_ssl.c>
    <VirtualHost *:443>
        ServerName mylemmydomain.com
        SSLEngine on
        ProxyRequests On
        ProxyPreserveHost On
        ProxyTimeout 600

        SSLCertificateFile /etc/letsencrypt/live/mylemmydomain.com/fullchain.pem
        SSLCertificateKeyFile /etc/letsencrypt/live/mylemmydomain.com/privkey.pem
#       ProxyPreserveHost On

        # Proxy pictshare
        <Location "/pictshare">
                ProxyPass http://internallemmyip:8537/
                ProxyPassReverse http://internallemmyip:8537/
        </Location>

        # Proxy iframely
        <Location "/iframely">
                ProxyPass http://internallemmyip:8061/
                ProxyPassReverse http://internallemmyip:8061/
        </Location>


#        # Correctly proxy websocket traffic
        RewriteEngine On
        RewriteCond %{HTTP:Upgrade} websocket [NC]
        RewriteRule /(.*) ws://internallemmyip:80/$1 [P,L]
#
        # Proxy Lemmy
        <Location "/">
                ProxyPass http://internallemmyip/
                ProxyPassReverse http://internallemmyip/
        </Location>

        ErrorLog /var/log/apache2/mylemmydomain-error.log
    </VirtualHost>
</IfModule>

SamSpudd
link
fedilink
31Y

Can confirm this works, though make sure to use a reverse proxy to enable HTTPS afterwards.

Wintermute
link
fedilink
11Y

Looking at your configs, it looks like you are telling Lemmy to use gmail for email. If that’s the case, do you need the postfix-relay image in docker? Postfix is basically just an smtp server, so logically whatever in lemmy sends stuff to postfix could just as well send it direct to gmail, but I’m not sure if it’s that simple.

@szeraax
creator
mod
admin
link
fedilink
11Y

I love it when people in the community point out details that can improve the service. Thank you for thinking of this!

Just set up my lemmy instance with this, and a modified Apache config:

<VirtualHost *:443>
        ServerName lemmy.deadca.de
        SSLEngine on
        ProxyRequests on
        ProxyPreserveHost on
        ProxyTimeout 600

        SetEnv proxy-nokeepalive 1
        SetEnv proxy-sendchunked 1

        <Location />
                Allow from all
                ProxyPass http://127.0.0.1:(INTERNAL LEMMY PORT)/
                ProxyPassReverse http://127.0.0.1:(INTERNAL LEMMY PORT)/
        </Location>

    ErrorLog "ERROR LOG LOCATION"
    CustomLog "ACCESS LOG LOCATION" common

    # Enable mod_rewrite (requires "a2enmod rewrite")
    RewriteEngine on

    # WebSockets support (requires "a2enmod rewrite proxy_wstunnel")
    RewriteCond %{HTTP:Upgrade} websocket [NC]
    RewriteCond %{HTTP:Connection} upgrade [NC]
    RewriteRule ^/?(.*) "ws://127.0.0.1:(INTERNAL LEMMY PORT)/$1" [P,L]

SSLCertificateFile FULLCHAIN.PEM LOCATION
SSLCertificateKeyFile PRIVKEY.PEM LOCATION
Include /etc/letsencrypt/options-ssl-apache.conf
</VirtualHost>
Tmpod
link
fedilink
11Y

Looks good, just have a couple of comments:

You’re pretty much good to go. Login to your lemmy instance. You should be able to use your docker host ip at your defined port OR via your reverse proxy lemmy domain host name.

docker-compose up

Shouldn’t you start the server before you’re able to login? 🙃

[on updating images] docker-compose down docker-compose up

You don’t actually need to bring the services down, you can just issue a up command and it will pull the new images and recreate the containers if need be.

Why are the docker tags for lemmy and lemmy-ui “latest” for arm64/v8? Shouldn’t there be a latest-arm and a latest-x86 or something? Annoying that I have to pin my lemmy images to a specific version in docker. I would prefer to let them be set to 1 image that gets updated and have watchtower deal with updating the image on a schedule of my choosing.

Pinning versions is generally a better idea. Gives you more control over the update process and is more easily revertable in case there’s some bug in a new release. As for the arm thing, I believe that’s just due to an oversight on the CI. My guess is that it’s tagging latest on all the targets its building for, and that arm64/v8 happens to be the last. Would nee to confirm though.

Finally, I’d like to ask for you to also include the link to my paste, if you don’t mind.

Thank you for piecing together this post!

@szeraax
creator
mod
admin
link
fedilink
21Y

Updated the 3 items you suggested. Thanks for the tip about updating images!

For pinning, that means that I have to keep a watchful eye over my image sources. I agree that having each release tagged is good to be able to easily go back without having to do the digest. But since the only way to run the latest image is via the tag 0.17.3, my watchtower has no way to upgrade automatically one 0.18.x comes out. That is a long term maintenance nightmare, imo.

Tmpod
link
fedilink
11Y

I can see where you’re coming from, but I really don’t find it an hassle. When I first started experimenting with hosting Lemmy (which then became lemmy.pt), the project was in version 0.12. I’ve updated to every single micro tagged release, and it’s just a matter of SSH’ing, editing the file, issuing docker compose up and the instance is updated in a few minutes. With exception of a couple of cases where a more complex migration was needed, it has always been the case.
You could even make a script for it, if you really wanted xD

@szeraax
creator
mod
admin
link
fedilink
11Y

Its not that doing updates for lemmy is hard. its that I have a few dozen services running concurrently. I use Watchtower to keep them up to date. I don’t want to have to watch each and every service manually. Today alone, I see in my discord log from Watchtower that calibre-web, plex, photoprism, monica, and shlink got updated to the latest version.

Tmpod
link
fedilink
11Y

I see, that seems neat, but at the same time I don’t know how safe that is. How does watchtower work?

@szeraax
creator
mod
admin
link
fedilink
11Y

Watchtower is granted access over your docker admin socket, so yes, it can administrate other containers at will. It will use whatever schedule you want and update images to latest for any containers that aren’t running the latest image. You can also configure it to email or otherwise notify out when it makes changes so that it is that much easier to have a log of what was changed and when, should you need to revert.

Tmpod
link
fedilink
11Y

That sounds pretty neat yeah. I’m sure it is also able to update images based on pinned tags, no?

@szeraax
creator
mod
admin
link
fedilink
11Y

All of my other services are pinned to the “latest” tag. or “latest-alpine”, so I don’t actually have them pinning to a specific release. If I specific a release tag, like what I had to do with lemmy’s 0.17.3, it will only stay on the tag until I manually update it.

notdeadyet
link
fedilink
2
edit-2
1Y

deleted by creator

notdeadyet
link
fedilink
1
edit-2
1Y

deleted by creator

@szeraax
creator
mod
admin
link
fedilink
21Y

Did you verify that you can access your JSON bits with the curl command?

No issues from my end, I can comment on other instances fine. I am also using N.P.M. and just did a simple forward, no location or other advanced bits.

notdeadyet
link
fedilink
1
edit-2
1Y

deleted by creator

@szeraax
creator
mod
admin
link
fedilink
21Y

I don’t recall if I actually sub to anything from lemmy.ml. Lemme go test.

notdeadyet
link
fedilink
1
edit-2
1Y

deleted by creator

@szeraax
creator
mod
admin
link
fedilink
11Y

Can you see this comment I made? https://lemmy.ml/comment/470458

notdeadyet
link
fedilink
1
edit-2
1Y

deleted by creator

notdeadyet
link
fedilink
1
edit-2
1Y

deleted by creator

Awesome write-up! Thanks!

@szeraax
creator
mod
admin
link
fedilink
2
edit-2
1Y

A comment appeared! Huzzah

I dare you to ban me for this!

Meta
!meta
Create a post

News, updates, warnings, etc.

  • 1 user online
  • 2 users / day
  • 3 users / week
  • 4 users / month
  • 8 users / 6 months
  • 55 subscribers
  • 2 Posts
  • 22 Comments
  • Modlog