Self-hosting miniflux with docker and provisioning with Ansible

I have been using miniflux for a while now, and I really like it. It’s a great RSS reader.

Here I want to share my experience with self-hosting it with docker and nginx.

The provisioning is done with Ansible.

Pretty simple, and I think it’s a good starting point for anyone who wants to self-host miniflux.

TLDR;

  • Provisioning with Ansible
  • Running miniflux with docker
  • Reverse proxy with nginx

Provisioning with Ansible

The basic idea is to spin up the docker container with miniflux, providing all the necessary env variables to configure it correctly.

Then you can configure nginx to reverse proxy the miniflux container.

E.g. you could host it on https://rss.example.com and then let nginx route the traffic to the container running miniflux.

Running miniflux with docker

You’ll need to install the community.docker ansible galaxy plugin:

ansible-galaxy collection install community.docker

The docker compose file (translated in an Ansible task) looks like this, and is located in roles/miniflux/tasks/main.yml:


- community.docker.docker_compose:
    project_name: miniflux
    definition:
      version: '2'
      services:
        db:
          image: postgres
          restart: unless-stopped
          environment:
            POSTGRES_PASSWORD: "{{ miniflux_password }}"
            POSTGRES_USER: "{{ miniflux_username }}"
          volumes:
            - miniflux-db:/var/lib/postgresql/data
        miniflux:
          image: miniflux/miniflux
          restart: unless-stopped
          environment:
            DATABASE_URL: "postgres://{{ miniflux_username }}:{{ miniflux_password }}@db/miniflux?sslmode=disable"
            RUN_MIGRATIONS: 1
            CREATE_ADMIN: 1
            ADMIN_USERNAME: "{{ miniflux_admin_username }}"
            ADMIN_PASSWORD: "{{ miniflux_admin_password }}"
            POLLING_FREQUENCY: 10
            BASE_URL: "https://{{ miniflux_domain }}"
            HTTPS: on"
          ports:
            - "127.0.0.1:3000:8080"
          depends_on:
            - db
      volumes:
        miniflux-db:
          driver: local
  register: output

- ansible.builtin.assert:
    that:
      - "output.services.miniflux.miniflux_miniflux_1.state.running"
      - "output.services.db.miniflux_db_1.state.running"

As you can see, we’re running a postres container alongside miniflux, exposing the miniflux container port 8080 to the host port 3000. This will later be used by nginx to reverse proxy the traffic.

We’re also setting various environment variables to configure miniflux, and then asserting the services are running.

This means you’ll also need to configure a file containing the variables / secrets.

You could do this in a group_vars file, or in a secrets file.

I’ll use ansible-vault to decrypt when editing and then encrypt the secrets file, like this

#create a new secrets file
touch secrets
#encrypt and decrypt it providing a vault password
ansible-vault encrypt secrets
ansible-vault decrypt secrets

The secrets file looks like this:

miniflux_username: miniflux
miniflux_password: <your password>
miniflux_admin_username: <your username>
miniflux_admin_password: <your password>
miniflux_domain: <your domain>

Reverse proxy with nginx

The nginx configuration looks like this, put it in roles/miniflux/templates/miniflux.conf.j2:


upstream miniflux {
    server 127.0.0.1:3000 max_fails=5 fail_timeout=60s;
}

server {
    server_name    {{ miniflux_domain }};

    listen         80;
    listen         [::]:80;

    gzip_vary on;
    gzip_proxied any;
    gzip_comp_level 6;
    gzip_buffers 16 8k;
    gzip_http_version 1.1;
    gzip_types text/plain text/css application/json application/javascript text/xml application/xml application/xml+rss text/javascript application/activity+json application/atom+xml;

    client_max_body_size 16m;
    ignore_invalid_headers off;

    proxy_http_version 1.1;
    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection "upgrade";
    proxy_set_header Host $http_host;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

    location / {
        proxy_pass http://miniflux;
    }
}

In the playbook, we copy the template to the nginx sites-available directory, and then symlink it to the sites-enabled directory.

- name: install nginx
  become: true
  apt:
    name: nginx
    state: present

- name: rm /etc/nginx/sites-enabled/default
  file:
    path: /etc/nginx/sites-enabled/default
    state: absent

- name: setup nginx vhost
  template:
    src=miniflux.conf.j2
    dest=/etc/nginx/sites-available/miniflux.conf

- name: symlink nginx vhost
  file:
    src=/etc/nginx/sites-available/miniflux.conf
    dest=/etc/nginx/sites-enabled/miniflux.conf
    state=link

- name: reload nginx
  service:
    name=nginx
    state=reloaded

PS: you could use handlers here, instead of reloading the service directly, I took it out for simplicity.

The Ansible project

Before running the playbook you need to configure the hosts file.

Create a new file called hosts and add the following:

<your server name> ansible_host=<your ip> ansible_user=<user> ansible_connection=ssh ansible_ssh_private_key_file=<ssh key> ansible_ssh_port=22

Also, add a requirements.yml file to install some dependencies with ansible-galaxy install -r requirements.yml:

---
roles:
  - name: geerlingguy.pip
  - name: geerlingguy.docker

Your project structure should look like this:

.
├── hosts
├── playbook.yml
├── requirements.yml
├── roles
│   ├── app-miniflux
│   │   ├── tasks
│   │   │   └── main.yml
│   │   └── templates
│   │       └── miniflux.conf.j2
└── secrets

Running the playbook

Now you can run the playbook:

ansible-playbook -i hosts miniflux.yml --ask-vault-pass

You’ll be prompted for the vault password you set when encrypting the secrets file.

Here, have a slice of pizza 🍕