Homepage + Docker + Gitlab + Ansible

I was watching TechnoTim recently with his video about homepage. Homepage is a dashboard for all your home lab links. If you have used heimdall before, it is similar, but all the configuration is in yaml.

Since the config (Tim's write up has some exellent starting points) is in yaml, I created a simple repo in my local gitlab repo to help me control the configuration and back it up. This is quite straight forward and I'm not going to cover how to do that here.

Similarly, I set up another repo for my ansible playbook and started to look at deploying the container for homepage. Again, thanks to TechnoTim, I use Traefik on the front end of my docker containers to get TLS configured properly on each one. You should also configure ansible to able to log in to your host which is running the docker containers.

Much as TechnoTim seems to create folders for configs, I'm just going to use a standard volume for my deployment. This places in /var/lib/docker/volumes. It also gives the folder a _data subdirectory. Once you have updated the variables below, the playbook should create a volume, based on the containers name. I set the container as a variable, as I may well want to template this later on.

---
  - name: "Homepage on Docker Playbook"
    hosts: your.docker.host
    become: yes
    become_method: sudo

    vars:
    - container: homepage
    - repo: 'https://gitlab.repo/userid/homepage-config.git'
    - domain: 'your.domain'

    vars_prompt:
    - name: "gitlab_username"
      prompt: "Enter your gitlab username"
      private: no
    - name: "gitlab_password"
      prompt: "Enter your gitlab password"
      private: yes

    tasks:

    - name: Create the homepage configuration volume
      docker_volume:
        name: "{{ container }}"
      tags: volumecreate

Now that we have a volume that can be read by our container, we need to populate it. This is going to be done via git, and has a few hurdles. Namely, authentication. You could set the repo to public, but consider where it is and what you have stored. For this, I opted to be prompted for my gitlab creds (the vars_prompt part) and then cache them for the checkout.

    - name: Add a setting to ~/.gitconfig
      community.general.git_config:
        name: safe.directory
        scope: global
        value: /var/lib/docker/volumes/{{ container }}/_data

    - name: Configure Git credential storage
      community.general.git_config:
        name: credential.helper
        scope: global
        value: store

    - name: Populate the Git credential store
      template:
        src: gitlab-creds-template.j2
        dest: ~{{ ansible_user }}/.git-credentials
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: u=rw,g=,o=
      no_log: true

    - name: Populate the configuration directory
      ansible.builtin.git:
        repo: "{{ repo }}"
        dest: /var/lib/docker/volumes/{{ container }}/_data
      environment:
        GIT_TERMINAL_PROMPT: 0
      tags: gitpopulate

The template for the credentials looked like this:

https://{{ gitlab_username|urlencode }}:{{ gitlab_password|urlencode }}@gitlab.repo

gitlab-creds-template.j2

If you need to debug anything so far, commenting out the no_log: true may help you in the short term, just don't forget to uncomment it later.

If everything is working up to this point, we need to deploy the container. This was quite straightforward to achieve, apart from the labels:

    - name: Start Homepage and apply labels
      docker_container:
        name: Homepage
        state: started
        networks:
        - name: proxy
        image: ghcr.io/gethomepage/homepage:latest
        env:
          PUID: "1000"
          PGID: "1000"
        volumes:
        - "{{ container }}:/app/config"
        labels: "{{ my_labels }}"
      tags: deployhomepage

At this stage, deplying the container through ansible will wipe out any config you may have set up previously, such as enviroment variables and labels.

I wanted to be clever with my labels, as I wanted to turn this playbook in to a template of sorts. That meant that the traefik labels needed to be dynamic. After much googling, I found this configuration:

    - name: Create Traefik labels's dictionary
      set_fact:
        my_labels: "{{ my_labels | default({}) | combine ({ item.key : item.value }) }}"
      with_items:
      - { 'key': 'traefik.enable' , 'value': 'true'}
      - { 'key': 'traefik.docker.network', 'value': "proxy"}
      - { 'key': "traefik.http.middlewares.{{ container }}-https-redirect.redirectscheme.scheme", 'value': "https"}
      - { 'key': "traefik.http.routers.{{ container }}-secure.entrypoints",'value': "https"}
      - { 'key': "traefik.http.routers.{{ container }}-secure.rule",'value': "Host(`{{ container }}.{{ domain }}`)"}
      - { 'key': "traefik.http.routers.{{ container }}-secure.service",'value': "{{ container }}"}
      - { 'key': "traefik.http.routers.{{ container }}-secure.tls",'value': "true"}
      - { 'key': "traefik.http.routers.{{ container }}.entrypoints",'value': "http"}
      - { 'key': "traefik.http.routers.{{ container }}.middlewares",'value': "{{ container }}-https-redirect"}
      - { 'key': "traefik.http.routers.{{ container }}.rule",'value': "Host(`{{ container }}.{{ domain }}`)"}
      - { 'key': "traefik.http.services.{{ container }}.loadbalancer.server.port", 'value': "3000"}

Putting it all together, gives us a final playbook of:

---
  - name: "Homepage on Docker Playbook"
    hosts: your.docker.host
    become: yes
    become_method: sudo

    vars:
    - container: homepage
    - repo: 'https://gitlab.repo/userid/homepage-config.git'
    - domain: 'your.domain'

    vars_prompt:
    - name: "gitlab_username"
      prompt: "Enter your gitlab username"
      private: no
    - name: "gitlab_password"
      prompt: "Enter your gitlab password"
      private: yes

    tasks:

    - name: Create the homepage configuration volume
      docker_volume:
        name: "{{ container }}"
      tags: volumecreate

    - name: Add a setting to ~/.gitconfig
      community.general.git_config:
        name: safe.directory
        scope: global
        value: /var/lib/docker/volumes/{{ container }}/_data

    - name: Configure Git credential storage
      community.general.git_config:
        name: credential.helper
        scope: global
        value: store

    - name: Populate the Git credential store
      template:
        src: gitlab-creds-template.j2
        dest: ~{{ ansible_user }}/.git-credentials
        owner: "{{ ansible_user }}"
        group: "{{ ansible_user }}"
        mode: u=rw,g=,o=
      no_log: true

    - name: Create Traefik labels's dictionary
      set_fact:
        my_labels: "{{ my_labels | default({}) | combine ({ item.key : item.value }) }}"
      with_items:
      - { 'key': 'traefik.enable' , 'value': 'true'}
      - { 'key': 'traefik.docker.network', 'value': "proxy"}
      - { 'key': "traefik.http.middlewares.{{ container }}-https-redirect.redirectscheme.scheme", 'value': "https"}
      - { 'key': "traefik.http.routers.{{ container }}-secure.entrypoints",'value': "https"}
      - { 'key': "traefik.http.routers.{{ container }}-secure.rule",'value': "Host(`{{ container }}.{{ domain }}`)"}
      - { 'key': "traefik.http.routers.{{ container }}-secure.service",'value': "{{ container }}"}
      - { 'key': "traefik.http.routers.{{ container }}-secure.tls",'value': "true"}
      - { 'key': "traefik.http.routers.{{ container }}.entrypoints",'value': "http"}
      - { 'key': "traefik.http.routers.{{ container }}.middlewares",'value': "{{ container }}-https-redirect"}
      - { 'key': "traefik.http.routers.{{ container }}.rule",'value': "Host(`{{ container }}.{{ domain }}`)"}
      - { 'key': "traefik.http.services.{{ container }}.loadbalancer.server.port", 'value': "3000"}

    - name: Populate the configuration directory
      ansible.builtin.git:
        repo: "{{ repo }}"
        dest: /var/lib/docker/volumes/{{ container }}/_data
      environment:
        GIT_TERMINAL_PROMPT: 0
      tags: gitpopulate

    - name: Start Homepage and apply labels
      docker_container:
        name: Homepage
        state: started
        networks:
        - name: proxy
        image: ghcr.io/gethomepage/homepage:latest
        env:
          PUID: "1000"
          PGID: "1000"
        volumes:
        - "{{ container }}:/app/config"
        labels: "{{ my_labels }}"
      tags: deployhomepage

For future enhancements, I intend to deploy this via gitlab's CI/CD pipeline and a runner. The gitlab username and password can be specified via the command line.

As an Amazon Associate I earn from qualifying purchases.

If you have found this post useful, please consider donating.