Notes on Installing Lemmy With Docker and Caddy

LemmyHeader

Are you looking to run Lemmy behind an existing Caddy web server in a docker image? This one is for you!

Background

With recent changes Reddit is making to destroy third-party apps, I’ve been thinking more and more about why I’m using Reddit in the first place. And it looks like I’m definitely not alone. I’ve mostly unsubbed from popular frontpage subreddits that new accounts start out with and what kept me coming back were specialized subreddits with high-quality useful information like /r/LocalLLaMA.

Well for a little while now there has been a push to “federate” or decentralized social media software. I held off initially because, well, frankly the software situation just looked rough. But after diving into the LLM AI space, where things still are truly bleeding-edge, I feel like I’m much more tolerant of rough edges that have at least been sanded down by a lot of users before me.

This website is currently running on a pretty small VPS with only 1 CPU and 1 GB of RAM. I already had Caddy up and running via a docker-compose script mostly pulled from the offical Docker Hub image docs. That was easy enough and the Caddyfile I created was only a few lines of basic settings, yet still provided a super fast static site server with automatic Let’s Encrypt integration to make serving HTTPS rediculously easy.

Getting Lemmy running wasn’t too hard, but I wanted to add my own installation notes to the blog in case it helps someone else in a similar situation.


Step 1: First pass on configuration files

The official documentation for installing from Docker is pretty succinct and helpful. I made the ~/Lemmy directory as shown and downloaded the files as suggested. Technically, the last one won’t be necessary because that’s an Nginx configuration. You may find it useful to look at, I suppose.

mkdir ~/lemmy
cd ~/lemmy

wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/templates/docker-compose.yml
wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/examples/config.hjson -O lemmy.hjson
wget https://raw.githubusercontent.com/LemmyNet/lemmy-ansible/main/templates/nginx_internal.conf

At this point, you need to open up the docker-compose.yml file and change everything inside {{ }} chunks. I made a few more changes:

  1. I ripped out the ‘proxy’ service that was an nginx image
  2. for the lemmy image, I used ‘dessalines/lemmy:latest’
  3. for the lemmy-ui image, I used ‘dessalines/lemmy-ui:latest’
  4. I generated a long alphanumeric password and replaced {{ postgres_password }} with that (in pictrs and postgres)
  5. I had setup an A record in DNS for lemmy.animal-machine.com so I replaced {{ domain }} with that
  6. I ripped out the postfix service since I will not be enabling email

There was a helpful YouTube vdeo by Awesome Open Source that I watched while prepping for this install. In that video, he edits more user names than I think you have to. Following the directions replacing the marked segments and ripping out nginx and postfix segments were sufficient for me.

The last part of the initial configuration is to open up the lemmy.hjson file created and put that same postgres password from the previous file in the {{ postgres_password }} spots and replace the {{ domain }} chunks with the same hostname from step 5 above.

The folder for pictrs needs to be created and have permissions set:

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

The documentation describes how to optimize your database settings. I plugged in my meager setup and adjusted the values as suggested.

I think you would technically be able to start up the server with docker-compose at this point, but there are a few other changes that need to be made.


Step 2: Caddy reverse proxy setup

The official documentation has a page for Using Caddy as a reverse proxy, but I found that it didn’t work for me. The configuration as given resulted in syntax errors when I brought up the Caddy image. Keep in mind that I already have a basic server running with TLS support and I didn’t want to add Caddy as a service to the new Lemmy one.

Tackling the first problem, take the template provided in the docs and just put most of it within the site handler like this:

(caddy-common) {
    encode gzip
    header {
        -Server
        Strict-Transport-Security "max-age=31536000; include-subdomains;"
        X-XSS-Protection "1; mode=block"
        X-Frame-Options "DENY"
        X-Content-Type-Options nosniff
        Referrer-Policy  no-referrer-when-downgrade
        X-Robots-Tag "none"
    }
}

lemmy-site.com {
    import caddy-common
    reverse_proxy   http://lemmy_lemmy-ui_1:1234

    @lemmy {
            path    /api/*
            path    /pictrs/*
            path    /feeds/*
            path    /nodeinfo/*
            path    /.well-known/*
    }

    @lemmy-hdr {
            header Accept application/*
    }

    handle @lemmy {
            reverse_proxy   http://lemmy_lemmy_1:8536
    }

    handle @lemmy-hdr {
            reverse_proxy   http://lemmy_lemmy_1:8536
    }

    @lemmy-post {
            method POST
    }

    handle @lemmy-post {
            reverse_proxy   http://lemmy_lemmy_1:8536
    }
}

For me, ’lemmy-site.com’ above is ’lemmy.animal-machine.com’ because that is the ‘{{ domain }}’ text replacment from above and what I had setup in my DNS record. All of this went right under my block I already had defined for my main static page website.


Step 3: Docker-compose networks

The last piece of the puzzle is to create a docker network for the two setups to use for communication. Open up the Caddy docker-compose.yml you’re using and define services/caddy/networks to have a network called internalwebnet. I’m choosing to call this ‘internalwebnet’ but that’s arbitrary. Then make sure to define a network of the same name under networks

services:
  caddy:
    # [... snipped ...]
    networks:
      - internalwebnet

# This was also added
networks:
  internalwebnet:
    name: caddy_network

With that setup, we can open up the Lemmy docker-compose.yml and add the internalwebnet network to all the services and then the networks section so that they can communicate.

services:
  lemmy:
    # [... snipped ...]
    networks:
      - internalwebnet 

  lemmy-ui:
    # [... snipped ...]
    networks:
      - internalwebnet

  pictrs:
    # [... snipped ...]
    networks:
      - internalwebnet

  postgres:
    # [... snipped ...]
    networks:
      - internalwebnet

# This was also added
networks:
  internalwebnet:
    external:
      name: caddy_network

Finished!

At this point you should be able to start the docker images.

cd ~/caddy
sudo docker-compose up -d
cd ~/lemmy
sudo docker-compose up -d && sudo docker-compose logs -f

The last command also automatically tails the logs so you can watch and make sure that everything is found appropriately. A ctrl-c will kill that when you’re finished without stopping the Lemmy server.

Feel free to discuss this post on in my Lemmy community over here.