Hello, I thought I’d share my own setup with Ansible.
Two motivations that played a factor here. First, I wanted to use Podman instead of Docker and second, I already have an Nginx Proxy that I wanted to use it. Lastly, I like managing my containers through systemd, which is very easy to do with Podman.
Tested on Debian 11, though it should work on most other distros as well.
Do look over the playbook, there might be some decisions you don’t agree with. For example, the different directories I’m creating for the various containers. (I’m creating multiple directories under /mnt)
Other variables, mainly logins, are already modifyable using the Ansible vault file included here.
Requirements
- A Server
- SSH access to the server
- Ansible Inventory file
- Basic knowledge of Ansible
- Basic knowledge of Nginx
- SMTP server EDIT 2023-06-15
Setup
Vault file
I’ll start with the vault file. Enter your values between the quotes. Explainations for most of them can be found in the lemmy.hjson
config file.
Filename: vault.yml
Content of vault.yml
# postgres
var_postgres_user: ""
var_postgres_password: ""
# pictrs
var_pictrs_api_key: ""
# smtp (lemmy config)
var_smtp_server: ""
var_smtp_login: ""
var_smtp_password: ""
var_smtp_from: ""
var_smtp_tls: ""
# initial admin config
var_admin_username: ""
var_admin_password: ""
var_site_name: ""
var_admin_email: ""
# network settings
var_hostname: ""
Encrypt your file with this command.
$ ansible-vault encrypt vault.yml
You can also view or edit the file by replacing the encrypt
keyword with view
or edit
respectively.
Lemmy config
Here’s the lemmy config I used. It is mostly copied from the default config example, though a lot of the values have been replaced by the variables you just filled in above.
(btw, federation still does work with tls_enabled: true
commented like this. As proof, I’m writing this post from my own instance set up this way)
Content of lemmy.hjson
{
# settings related to the postgresql database
database: {
# Username to connect to postgres
user: "{{ var_postgres_user }}"
# Password to connect to postgres
password: "{{ var_postgres_password }}"
# Host where postgres is running
host: "lemmy-db"
# Port where postgres can be accessed
port: 5432
# Name of the postgres database for lemmy
database: "lemmy"
# Maximum number of active sql connections
pool_size: 5
}
# Settings related to activitypub federation
# Pictrs image server configuration.
pictrs: {
# Address where pictrs is available (for image hosting)
url: "http://lemmy-pictrs:8080/"
# Set a custom pictrs API key. ( Required for deleting images )
api_key: "{{ var_pictrs_api_key }}"
}
# Email sending configuration. All options except login/password are mandatory
email: {
# Hostname and port of the smtp server
smtp_server: "{{ var_smtp_server }}"
# Login name for smtp server
smtp_login: "{{ var_smtp_login }}"
# Password to login to the smtp server
smtp_password: "{{ var_smtp_password }}"
# Address to send emails from, eg "noreply@your-instance.com"
smtp_from_address: "{{ var_smtp_from }}"
# Whether or not smtp connections should use tls. Can be none, tls, or starttls
tls_type: "{{ var_smtp_tls }}"
}
# Parameters for automatic configuration of new instance (only used at first start)
setup: {
# Username for the admin user
admin_username: "{{ var_admin_username }}"
# Password for the admin user. It must be at least 10 characters.
admin_password: "{{ var_admin_password }}"
# Name of the site (can be changed later)
site_name: "{{ var_site_name }}"
# Email for the admin user (optional, can be omitted and set later through the website)
admin_email: "{{ var_admin_email }}"
}
# the domain name of your instance (mandatory)
hostname: "{{ var_hostname }}"
# 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
}
Ansible Playbook
Now a quick overview of my playbook:
- Installs podman
- The systemd service for running the podman pod will be stopped. EDIT: The error will now be caught and continue
- Create various directories
- Copy the lemmy configuration
- Create a podman network
- Create a podman pod
- Port 1234 is for the Lemmy UI
- Port 8536 is the Lemmy backend
- Create all the containers
- Generate the systemd service for the pod
- Enable the systemd service
And here’s the Ansible playbook file.
Content of playbook.yml
---
- hosts: all
become: yes
become_method: sudo
vars:
var_lemmy_version: "0.17.4"
tasks:
- name: Install podman
ansible.builtin.package:
name:
- podman
state: latest
- name: Stop lemmy pod if necessary
block:
- name: Stop systemd service
ansible.builtin.systemd:
name: pod-pod_lemmy
state: stopped
rescue:
- name: Skip stopping systemd service
ansible.builtin.debug:
msg: "First time setup. Ignore the error above"
- name: Create database directory
ansible.builtin.file:
path: /mnt/lemmy-db
state: directory
owner: root
group: root
- name: Create lemmy directory
ansible.builtin.file:
path: /mnt/lemmy-app
state: directory
owner: root
group: root
- name: Create pictrs directory
ansible.builtin.file:
path: /mnt/lemmy-pictrs
state: directory
owner: 991
group: 991
- name: Copy lemmy config file
template:
dest: /mnt/lemmy-app/lemmy.hjson
src: ./lemmy.hjson
- name: Create lemmy network
containers.podman.podman_network:
name: net_lemmy
- name: Create lemmy pod
containers.podman.podman_pod:
name: pod_lemmy
network:
- net_lemmy
publish:
- "1234:1234" # lemmy-ui
- "8536:8536" # lemmy-app
- name: Create DB container
containers.podman.podman_container:
name: lemmy-db
image: docker.io/postgres:15-alpine
volume:
- /mnt/lemmy-db:/var/lib/postgresql/data
env:
POSTGRES_USER: "{{ var_postgres_user }}"
POSTGRES_PASSWORD: "{{ var_postgres_password }}"
POSTGRES_DB: lemmy
label:
io.containers.autoupdate: image
pod: "pod_lemmy"
state: "created"
- name: Create pictrs container
containers.podman.podman_container:
name: lemmy-pictrs
image: docker.io/asonix/pictrs:0.3.1
#entrypoint: "/sbin/tini -- /usr/local/bin/pict-rs -p /mnt -m 4 --image-format webp"
# flags: https://git.asonix.dog/asonix/pict-rs/src/tag/v0.3.1
command: "/usr/local/bin/pict-rs -p /mnt -m 4 --image-format webp"
user: 991:991
volume:
- /mnt/lemmy-pictrs:/mnt
env:
PICTRS__API_KEY: "{{ var_pictrs_api_key }}"
label:
io.containers.autoupdate: image
pod: "pod_lemmy"
state: "created"
- name: Create lemmy container
containers.podman.podman_container:
name: lemmy-app
image: docker.io/dessalines/lemmy:{{ var_lemmy_version }}
volume:
- /mnt/lemmy-app/lemmy.hjson:/config/config.hjson
interactive: true
tty: true
env:
RUST_LOG: "warn,lemmy_server=info,lemmy_api=info,lemmy_api_common=info,lemmy_api_crud=info,lemmy_apub=info,lemmy_db_schema=info,lemmy_db_views=info,lemmy_db_views_actor=info,lemmy_db_views_moderator=info,lemmy_routes=info,lemmy_utils=info,lemmy_websocket=info"
#requires:
# - lemmy-db
# - lemmy-pictrs
label:
io.containers.autoupdate: image
pod: "pod_lemmy"
state: "created"
- name: Create lemmy-ui container
containers.podman.podman_container:
name: lemmy-ui
image: docker.io/dessalines/lemmy-ui:{{ var_lemmy_version }}
env:
# this needs to match the hostname defined in the lemmy service
LEMMY_UI_LEMMY_INTERNAL_HOST: "lemmy-app:8536"
# set the outside hostname here
#LEMMY_UI_LEMMY_EXTERNAL_HOST: "{{ var_hostname }}"
LEMMY_UI_LEMMY_EXTERNAL_HOST: "{{ ansible_default_ipv4.address }}:1234"
#LEMMY_HTTPS: true
#requires:
# - lemmy-app
label:
io.containers.autoupdate: image
pod: "pod_lemmy"
state: "created"
- name: Create systemd service
containers.podman.podman_generate_systemd:
name: pod_lemmy
new: true
dest: /etc/systemd/system/
- name: Enable lemmy pod
ansible.builtin.systemd:
daemon_reload: true
name: pod-pod_lemmy
enabled: true
state: started
Run the playbook with this command.
$ ansible-playbook -i inventory.yml -e @vault.yml --ask-vault-pass playbook.yml -K
You will be prompted for the sudo password and the password you set for your encrypted vault.
If you authenticate to ssh using a password, add -k
to the above command and you’ll be prompted for that as well.
There’s a character limit on posts, so I’ll put the rest as a comment below.
The rest of my post.
(Fyi, the OP has 9048 characters including all the markdown)Problems
After executing the playbook, you should now have 4 containers plus the “infra” container running.
You might also notice the containers disappearing and reappearing for a bit before eventually running continuously.
A potential fix could be uncommenting therequires:
keys and list items in the playbook above. This should force some ordering on podman instead of launching everything at once and praying.
I haven’t been able to test that yet, as Debian 11 comes with podman 3.0.1, which lacks that feature. Newer podman versions have that flag (I know 4.3.1 in Debian 12 has it).
I did find that just rebooting the server produced results reliably, so maybe try that? ¯_(ツ)_/¯Nginx config
Finally my Nginx site configuration. It’s mostly taken from the official docs with only slight modifications to be used as a site instead of main config.
Replace the values [VALUE] with the config applicable to you.Content of Nginx site configuration
upstream lemmy { # this needs to map to the lemmy (server) docker service hostname server "[LEMMY SERVER IP]:8536"; } upstream lemmy-ui { # this needs to map to the lemmy-ui docker service hostname server "[LEMMY SERVER IP]:1234"; } server { server_name [DOMAIN]; listen *:443 ssl http2; ssl_certificate_key /etc/acme-sh/[DOMAIN]/key.pem; ssl_certificate /etc/acme-sh/[DOMAIN]/cert.pem; # Security / XSS Mitigation Headers add_header X-Frame-Options "SAMEORIGIN"; add_header X-XSS-Protection "1; mode=block"; add_header X-Content-Type-Options "nosniff"; # Compression gzip on; gzip_types text/css application/javascript image/svg+xml; gzip_vary on; # Upload limit, relevant for pictrs client_max_body_size 20M; # 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/activity+json") { set $proxpass "http://lemmy"; } if ($http_accept = "application/ld+json; profile=\"https://www.w3.org/ns/activitystreams\"") { 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; } }
That’s it, you should now have a running Lemmy instance.
P.S. More info about the character limit here for lemmy backend and here for the UI
Thanks for this writeup, will have an attempt when I’m back home.