Homelab: My Distributed Homelab

# homelab # networking # hardware
Published On: July 21, 2022 (Last updated on: April 15, 2024)
1482 words · 7 min

Networking

For the homelab, I would like to choose a commerical company’s product, as the service can be guaranteed by the company reputation. All my devices can connect to the WAN via NAT1 such that I don’t need the nat passthrough solution. I choose Tailscale as I mentioned, it performs well under my testing. Just follow the instruction and the networking problem is solved.

Service

For the homelab, I would like to docker as it is very covinence and can set up easily.

To install the docker, I just follow the intrduction from the document:

# uninstall old versions (if installed) 
sudo apt-get install remove docker docker-engine docker.io containere runc

# install using the repository
sudo apt-get update
sudo apt-get install ca-certificates curl gnupg lsb-release # some preliminary
sudo mkdir -p /etc/apt/keyrings # add gpg key
curl -fsSL https://download.docker.com/linux/ubuntu/gpg | sudo gpg --dearmor -o /etc/apt/keyrings/docker.gpg
## add key
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://download.docker.com/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# OPTIONAL: for China's user you may use mirrors.bfsu.edu.cn for a smooth download experience
echo \
  "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.gpg] https://mirrors.bfsu.edu.cn/docker-ce/linux/ubuntu \
  $(lsb_release -cs) stable" | sudo tee /etc/apt/sources.list.d/docker.list > /dev/null

# update apt repository and install docker and related packages
sudo apt-get update && sudo apt-get install docker-ce docker-ce-cli containerd.io docker-compose-plugin -y

# check docker verion
sudo docker version
# Client: Docker Engine - Community
#  Version:           20.10.18
#  API version:       1.41
#  Go version:        go1.18.6
#  Git commit:        b40c2f6
#  Built:             Thu Sep  8 23:11:43 2022
#  OS/Arch:           linux/amd64
#  Context:           default
#  Experimental:      true

# Server: Docker Engine - Community
#  Engine:
#   Version:          20.10.18
#   API version:      1.41 (minimum version 1.12)
#   Go version:       go1.18.6
#   Git commit:       e42327a
#   Built:            Thu Sep  8 23:09:30 2022
#   OS/Arch:          linux/amd64
#   Experimental:     false
#  containerd:
#   Version:          1.6.8
#   GitCommit:        9cd3357b7fd7218e4aec3eae239db1f68a5a6ec6
#  runc:
#   Version:          1.1.4
#   GitCommit:        v1.1.4-0-g5fd4c4d
#  docker-init:
#   Version:          0.19.0
#   GitCommit:        de40ad0

MultiMedia

There are multiple multimedia software, for istance, Plex, Emby, Jellyfin, and KODI. For some reason, I did not want to pay too much money of the media service as I just want a place that can show the poster and brief introduction of the video, and I don’t need too much “fancy” features. By comparison, I choose Jellyfin, the “open-source version of Emby”, as me multimedia server and I install it via the docker-compose.yml:

version: "2.1"
services:
  jellyfin:
    image: lscr.io/linuxserver/jellyfin:latest
    container_name: jellyfin
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=Asia/Shanghai
    volumes: # map the physical volume to the container
      - /path/to/library:/config       
      - /path/to/tvseries:/data/tvshows
      - /path/to/movies:/data/movies
    ports:
      - 8096:8096
    restart: always # when docker restart, the docker image will auto restart

Authentication

There are many authentication software that support Oauth, for instance, Authentik, Okta, auth0, Ory Hydra, KeyCloak, an there are some authentication cloud services, like authing.com. By comparsion, I choose Authentik and KeyCloak, authentik has a good-looking interface, and KeyCloak is a widely used software, which mean there are more example and solution on the interest. The authentik is used for my self-hosted services, and the KeyCloak is for the other serive that I need to provide to other users. Similarly, I use docker-compose.yml to set up these services:

# Authentik
# you need to set up the ENV parameters on other file call `.env`, check https://goauthentik.io/docs/installation/docker-compose for the detail
version: '3.4'

services:
  postgresql:
    image: docker.io/library/postgres:12-alpine
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -d $${POSTGRES_DB} -U $${POSTGRES_USER}"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 5s
    volumes:
      - database:/var/lib/postgresql/data
    environment:
      - POSTGRES_PASSWORD=${PG_PASS:?database password required}
      - POSTGRES_USER=${PG_USER:-authentik}
      - POSTGRES_DB=${PG_DB:-authentik}
    env_file:
      - .env
  redis:
    image: docker.io/library/redis:alpine
    command: --save 60 1 --loglevel warning
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cli ping | grep PONG"]
      start_period: 20s
      interval: 30s
      retries: 5
      timeout: 3s
    volumes:
      - redis:/data
  server:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.9.0}
    restart: unless-stopped
    command: server
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      # AUTHENTIK_ERROR_REPORTING__ENABLED: "true"
    volumes:
      - ./media:/media
      - ./custom-templates:/templates
      - geoip:/geoip
    env_file:
      - .env
    ports:
      - "0.0.0.0:${AUTHENTIK_PORT_HTTP:-9000}:9000"
      - "0.0.0.0:${AUTHENTIK_PORT_HTTPS:-9443}:9443"
  worker:
    image: ${AUTHENTIK_IMAGE:-ghcr.io/goauthentik/server}:${AUTHENTIK_TAG:-2022.9.0}
    restart: unless-stopped
    command: worker
    environment:
      AUTHENTIK_REDIS__HOST: redis
      AUTHENTIK_POSTGRESQL__HOST: postgresql
      AUTHENTIK_POSTGRESQL__USER: ${PG_USER:-authentik}
      AUTHENTIK_POSTGRESQL__NAME: ${PG_DB:-authentik}
      AUTHENTIK_POSTGRESQL__PASSWORD: ${PG_PASS}
      # AUTHENTIK_ERROR_REPORTING__ENABLED: "true"
    # This is optional, and can be removed. If you remove this, the following will happen
    # - The permissions for the /media folders aren't fixed, so make sure they are 1000:1000
    # - The docker socket can't be accessed anymore
    user: root
    volumes:
      - ./media:/media
      - ./certs:/certs
      - /var/run/docker.sock:/var/run/docker.sock
      - ./custom-templates:/templates
      - geoip:/geoip
    env_file:
      - .env

volumes:
  database:
    driver: local
  redis:
    driver: local
# KeyClock
version: '3'

services:
  postgres:
      image: postgres
      volumes:
        - ./data:/var/lib/postgresql/data
      environment:
        POSTGRES_DB: keycloak
        POSTGRES_USER: keycloak
        POSTGRES_PASSWORD: password

  keycloak:
      image: keycloak/keycloak:latest
      environment:
        DB_VENDOR: POSTGRES
        DB_ADDR: postgres
        DB_DATABASE: keycloak
        DB_USER: keycloak
        DB_SCHEMA: public
        DB_PASSWORD: password       # change it if you want
        KEYCLOAK_USER: admin
        KEYCLOAK_PASSWORD: Pa55w0rd # change it if you want 
        PROXY_ADDRESS_FORWARDING: "true"
        REDIRECT_SOCKET: "proxy-https"
      ports:
        - 8080:8080
      depends_on:
        - postgres

Working and Coding

Most of the time, I just use the local environment for the documentation and coding, however, sometimes I will re-build the computer and to avoid the data loss. Thus, I choose outline for the documentation, dokuwiki for building the wiki system, [Nexcloud] as my personal Net storage, and Gitea as my self-hosted code repository. Outline is a Nation-alternative software which also provide a beatiful, realtime knowledge base, where I can easily store my throught and idea wia the web. Dokuwiki is a well-known wiki system without a database backend, which means I can easily migrate it to any place. Between Gitea and Gitlab, I choose Gitea at last because GitLab’s hardware requirement is too high that I don’t have enough resources to run a GitLab and I just need a place to store my code, not those extra features.

# outline 
# you need to set up the ENV parameters on other file call `.env`, check https://app.getoutline.com/s/770a97da-13e5-401e-9f8a-37949c19f97e/doc/docker-7pfeLP5a8t for the detail
version: "3"
services:

  outline:
    image: outlinewiki/outline
    env_file: ./docker.env
    command: sh -c "yarn sequelize:migrate --env=production-ssl-disabled && yarn start --env=production-ssl-disabled"
    ports:
      - "3000"3000"
    depends_on:
      - postgres
      - redis

  redis:
    image: redis
    env_file: ./docker.env
    ports:
      - "6379:6379"
    volumes:
      - ./redis.conf:/redis.conf
    command: ["redis-server", "/redis.conf"]
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 30s
      retries: 3

  postgres:
    image: postgres
    env_file: ./docker.env
    ports:
      - "5432:5432"
    volumes:
      - database-data:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready -U user"]
      interval: 30s
      timeout: 20s
      retries: 3
    environment:
      POSTGRES_USER: user       # change it if you want
      POSTGRES_PASSWORD: pass   # change it if you want
      POSTGRES_DB: outline

  storage:
    image: minio/minio:latest
    env_file: ./docker.env
    ports:
      - "9000:9000"
      - "9001:9001"
    entrypoint: sh
    command: -c 'minio server /data --console-address ":9001"'
    deploy:
      restart_policy:
        condition: on-failure
    volumes:
      - /mnt/minio/data:/data
      - /mnt/minio/config:/root/.minio
    healthcheck:
      test: ["CMD", "curl", "-f", "http://storage:9000/minio/health/live"]
      interval: 30s
      timeout: 20s
      retries: 3

volumes:
  storage-data:
  database-data
# dokuwiki
version: '2'
services:
  dokuwiki:
    image: docker.io/bitnami/dokuwiki
    ports:
      - '8089:8080'
    volumes:
      - ./dokuwiki:/bitnami/dokuwiki'
# Nextcloud
version: '2'

services:
  app:
    container_name: nextcloud_app
    image: nextcloud
    restart: always
    ports:
      - 10000:80
    environment:
      - DATABASE_URL=mysql+pymysql://nextcloud:nextcloud@db/nextcloud
      - REDIS_URL=redis://redis:6379
    volumes:
      - /opt/nextcloud/www:/var/www/html
    depends_on:
      - db
      - redis
    networks:
        default:
        internal:

  db:
    container_name: nextcloud_db
    image: mariadb:10.5
    restart: always
    environment:
      - MYSQL_ROOT_PASSWORD=nextcloud   # change it if you want
      - MYSQL_USER=nextcloud            # change it if you want
      - MYSQL_PASSWORD=nextcloud        # change it if you want
      - MYSQL_DATABASE=nextcloud        # change it if you want
    volumes:
      - /opt/nextcloud/db:/var/lib/mysql
    networks:
        internal:
    command: --transaction-isolation=READ-COMMITTED --binlog-format=ROW

  redis:
    container_name: nextcloud_redis
    image: redis
    volumes:
      - /opt/nextcloud/redis/data:/data
      - /opt/nextcloud/redis/redis.conf:/etc/redis/redis.conf
    restart: always
    networks:
        internal:
    command: redis-server /etc/redis/redis.conf --appendonly yes

networks:
    default:
    internal:
        internal: true
# Gitea
version: "3"

networks:
  gitea:
    external: false

services:
  server:
    image: gitea/gitea:latest
    container_name: gitea
    environment:
      - USER_UID=1000
      - USER_GID=1000
      - GITEA__database__DB_TYPE=postgres
      - GITEA__database__HOST=db:5432   # change it if you want
      - GITEA__database__NAME=gitea     # change it if you want
      - GITEA__database__USER=gitea     # change it if you want
      - GITEA__database__PASSWD=gitea   # change it if you want
    restart: always
    networks:
      - gitea
    volumes:
      - ./gitea:/data
      - /etc/timezone:/etc/timezone:ro
      - /etc/localtime:/etc/localtime:ro
    ports:
      - "3000:3000"
      - "1022:22"
    depends_on:
      - db

  db:
    image: postgres:14
    restart: always
    environment:
      - POSTGRES_USER=gitea
      - POSTGRES_PASSWORD=gitea
      - POSTGRES_DB=gitea
    networks:
      - gitea
    volumes:
      - ./postgres:/var/lib/postgresql/data

RSS Feed Reader

I use Miniflux as my RSS Feed Reader, the reason why I give Tiny Tiny RSS is that Miniflux V2 is written by Go-lang and I don’t like PHP.

version: '3.4'
services:
  miniflux:
    image: miniflux/miniflux:latest
    ports:
      - "8080:8080"
    depends_on:
      - db
    environment:
      - DATABASE_URL=postgres://miniflux:secret@db/miniflux?sslmode=disable
      - RUN_MIGRATIONS=1
      - CREATE_ADMIN=1
      - ADMIN_USERNAME=admin
      - ADMIN_PASSWORD=Pa55word
      - OAUTH2_USER_CREATION=true
      - OAUTH2_PROVIDER=oidc
      - OAUTH2_CLIENT_ID=<oauth-client-id>
      - OAUTH2_CLIENT_SECRET=<oauth-client-secret>
      - OAUTH2_REDIRECT_URL=<miniflux-url>oauth2/oidc/callback
      - OAUTH2_OIDC_DISCOVERY_ENDPOINT=<oidc-endpoint>
  db:
    image: postgres:latest
    environment:
      - POSTGRES_USER=miniflux
      - POSTGRES_PASSWORD=secret
    volumes:
      - miniflux-db:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD", "pg_isready", "-U", "miniflux"]
      interval: 10s
      start_period: 30s
volumes:
  miniflux-db:

Key and Password Management

There are multiple different password manager, Google Password Manager, 1Password, LastPass, etc.. But I choose Bitwarden as I can self-host the software on my own machine and I don’t need to worry about the data leakage.

version: "3"
services:
  bitwarden:
    image: bitwardenrs/server
    container_name: bitwardenrs
    restart: always
    ports:
        - "127.0.0.1:8000:80"
        - "127.0.0.1:3012:3012"
    volumes:
      - ./bw-data:/data
    environment:
      WEBSOCKET_ENABLED: "true"
      SIGNUPS_ALLOWED: "false"
      WEB_VAULT_ENABLED: "true"
      ADMIN_TOKEN: "<admin-token>" # you can generate the token by using openssl: `openssl rand -hex 32`