HomeLab with Docker and Raspberry Pi 5.

HomeLab with Docker and Raspberry Pi 5.
Photo by Ian Taylor / Unsplash

A HomeLab is a personal, often DIY (do-it-yourself) setup of computer servers and networking equipment used for learning, experimentation, and sometimes practical tasks like hosting services.

I have a couple Raspberry Pis as home servers where I can do quick proof of concepts, unit testing, code analysis and a lot more.

I will give you my personal favorites for software development, sometime later I will add the fun ones.

Let's begin

Raspberry Pi

I would recommend a Raspberry Pi 5, but you can use another computer.

Raspberry Pi 5

  • CPU: Quad-core ARM Cortex-A76 @ 2.4 GHz (64-bit)
  • RAM: 4GB or 8GB LPDDR4X (There is a 16 GB but for the price you will be better off with an old laptop, I recommend the 8 GB size)
  • GPU: Broadcom VideoCore VII, supports dual 4Kp60 output (You can run Ollama, slow but it works, in another post we will be doing that from docker)
  • microSD card slot (Recommended for small workloads and POCs with an 128 GB size)
  • PCIe 2.0 x1 (for NVMe SSD via adapter, if you want to do a more permanent setup you can buy an NVMe hat and the SSD, however for practical purpose we will be using a microSD)
  • Gigabit Ethernet (Preferred as it will have a bunch of traffic)
  • Wi-Fi 802.11ac (dual band)
  • Power: USB-C PD (Power Delivery), 5V/5A

Setup the Raspberry Pi

Flash the SD card with the OS (same with NVMe).

Download the imager from

https://www.raspberrypi.com/software/

Choose the appropriate device

Select 64 bit

Edit the settings to enable Wi-Fi and ssh, I named the host as homelab and i will be using the madeup user name homelabadmin.

Once you finish flashing, insert the card, turn on the Pi and connect via SSH (cable or wifi).

I use windows terminal, awesome product BTW, but you can use whatever you feel comfortable with. I hope to write a post on how you can customize the terminal.

The command to connect is

ssh homelabadmin@homelab

We are ready to install all the software now.

Docker and software

Updates

As a rule, always update the latest version of the apt

sudo apt update
sudo apt upgrade -y

Docker

Next is Docker, we will download a script from docker and execute it

curl -sSL https://get.docker.com | sh

And add the current user to the docker group.

sudo usermod -aG docker $USER

Now, type exit and login again with the ssh command, this is necessary for the current session shell to have the proper permissions.

Docker Compose

We will be using docker compose, it is simpler to give commands to docker and to stand up all the software and networking necessary for our projects.

Also managing the files is really easy once we install Portainer

Portainer

Portainer is a docker management UI tool, it handles the containers, images, logs, networks, etc.

Since we don't have installed docker desktop, you can use Portainer for the same purpose.

Now to create the first docker compose file, we will reuse these commands a lot.

  • Create the directory where the docker compose file will reside and potentially all the files needed to execute the container.
  • Change to the directory of the stack
  • Create the docker-compose file
  • Edit the docker-compose file
sudo mkdir -p /opt/stacks/portainer
cd /opt/stacks/portainer
touch docker-compose.yml
sudo nano docker-compose.yml

This will open the nano editor and you can copy-paste the following YAML

services:
  portainer:
    image: portainer/portainer-ce:latest
    container_name: portainer
    restart: always
    ports:
      - "8000:8000"
      - "9443:9443"
      - "9000:9000"
    volumes:
      - /var/run/docker.sock:/var/run/docker.sock
      - portainer_data:/data
volumes:
  portainer_data:

You can save the text with ctrl+x, y and then enter

Be wary of the spaces, yaml it is really opinionated.

A typical docker compose will follow these sections

🔧 services:

This section defines the containers (services) you want to run.

🧱 portainer:

This is the name of the service.

▶️ image: portainer/portainer-ce:latest

This tells Docker to use the latest version of the open-source Portainer Community Edition image.

🏷️ container_name: portainer

Names the container "portainer" instead of a random auto-generated name.

🔁 restart: always

Makes sure the container automatically restarts if it crashes or if the system reboots.

🌐 ports:

This maps ports from your computer to the container:

8000:8000: For Portainer's agent communication (optional, used for managing remote environments).

9443:9443: Portainer’s secure web interface (HTTPS).

9000:9000: Portainer’s classic web interface (HTTP).

So you can access Portainer via:

http://localhost:9000 (classic UI)

https://localhost:9443 (secure UI)

💾 volumes:

This is used to store data and share files between your computer and the container:

/var/run/docker.sock:/var/run/docker.sock:
Gives Portainer access to Docker itself, so it can manage containers on your system.

portainer_data:/data:
Stores Portainer’s settings and data in a named volume so it persists even if the container is deleted.

📦 volumes:

This bottom section defines the portainer_data volume that’s used above. It tells Docker to create a named volume called portainer_data.

Finally execute the command

docker compose up -d

If you get an error use

sudo docker compose up -d

We are letting docker know that there is a docker-compose.yml file in the current directory, bring it up and use detach mode, meaning it will run its own thread and give control to docker.

You can now browse to Portainer using

https://homelab:9443

You just need to follow the prompts to create an admin user, and you can see all the information about your containers.

Next step VS Code

VS Code

I like nano but sometimes copying and pasting text from one machine to another is broken, I usually install visual studio on the Pi and access it thru its web UI.

Execute the commands to create the VS Code directory stack

sudo mkdir -p /opt/stacks/vscode
cd /opt/stacks/vscode
touch docker-compose.yml
sudo nano docker-compose.yml

Use nano to create the docker-compose.yml file

services:
  code-server:
    container_name: vscode
    image: lscr.io/linuxserver/code-server:latest
    environment:
      - TZ=America/Los_Angeles
      - DEFAULT_WORKSPACE=/config
    ports:
      - 2443:8443
    volumes:
      - ./config:/config
      - /:/host
    user: "1000:1000"
    restart: unless-stopped

Docker compose up

docker compose up -d

Easy right! as you can see all the steps repeat for the next stacks

services:

Starts the list of container services to run.

🧠 code-server:

This is the name of the service (you can name it anything you want).

🏷️ container_name: vscode

Gives the container a fixed name ("vscode") instead of letting Docker assign a random one.

🐳 image: lscr.io/linuxserver/code-server:latest

Uses the latest version of the LinuxServer’s code-server image, which runs VS Code as a web app.

🌎 environment:

Sets environment variables inside the container:

TZ=America/Los_Angeles: Sets the time zone to match your local time.

DEFAULT_WORKSPACE=/config: Sets the default folder opened in VS Code when it launches.

🌐 ports:

2443:8443:
Maps port 8443 inside the container (code-server's default secure port) to port 2443 on your host machine.
So, you can access it by going to https://localhost:2443 in your browser.

💾 volumes:

These give the container access to files on your computer:

./config:/config:
Saves code-server settings and extensions in a folder called config (in the same directory as your docker-compose.yml), so your setup is saved between restarts.

/:/host:
Lets you browse your entire host file system inside code-server. This is powerful but risky—use with caution!

👤 user: "1000:1000"

Runs the container as a specific user (user ID 1000, group ID 1000), which is usually the first user created on a Linux system. This avoids permission problems when accessing files.

🔁 restart: unless-stopped

Automatically restarts the container unless you stop it manually.

You can use VS Code to create your POCs, editor, bash terminal, etc. In another post I will show you how to leverage this.

For now, it will serve us to create folders, files and edit them.  If you want you can install the plugin for docker and execute the docker compose command directly from the editor, but I preferr using the terminal for commands and the editor just to edit files.

Now I will not explain the next docker compose files in detail but will list the use and give you the contents.

Azurite

Azurite will emulate azure services such as

Great for low budget solutions

services:
  azurite:
     image: mcr.microsoft.com/azure-storage/azurite
     container_name: azurite
     hostname: azurite
     command: 'azurite --loose --blobHost 0.0.0.0 --blobPort 10000 --queueHost 0.0.0.0 --queuePort 10001 --tableHost 0.0.0.0 --location /workspace --debug /workspace/debug.log'
     ports:
      - 10000:10000
      - 10001:10001
      - 10012:10002
     volumes:
      - ~/dockervolumes/azurite:/workspace

No UI, but you can use storage explorer and .NET libraries to interact with it

Azure Storage Explorer – cloud storage management | Microsoft Azure
Easily manage your Azure storage accounts in the cloud, from Windows, macOS, or Linux, using Azure Storage Explorer.

Grafana with Postgres

Grafana is a tool that lets you visualize data—you can build beautiful dashboards and charts from your data sources. We will use Postgres to save the configuration from Grafana.

services:
  grafana_postgres:
    container_name: grafana_postgres
    image: postgres:15
    restart: always
    environment:
      POSTGRES_DB: grafana_db
      POSTGRES_USER: grafana_usr
      POSTGRES_PASSWORD: myweirdPassword!!!
    volumes:
      - postgres-storage:/var/lib/postgresql/data
  grafana:
    image: grafana/grafana-enterprise:latest
    container_name: grafana
    restart: unless-stopped
    environment:
      GF_DATABASE_TYPE: postgres
      GF_DATABASE_HOST: grafana_postgres
      GF_DATABASE_NAME: grafana_db
      GF_DATABASE_USER: grafana_usr
      GF_DATABASE_PASSWORD: myweirdPassword!!!
    ports:
      - '3200:3000'
    volumes:
      - grafana-storage:/var/lib/grafana
volumes:
  grafana-storage: {}
  postgres-storage: {}

Login using http://homelab:3200

Fun projects for Grafana

  • Speed monitor (execute speed tests and record them in Postgres and create a dashboard)
  • Weather (consume the us weather system rest Api endpoints and create a dashboard)
  • .NET telemetry (send .NET telemetry to Postgres and analyze it)

Hashicorp Vault

Create secrets and access them securely (I prefer Azure Key vault, but that costs money and we are building our own HomeLab)

services:
  vault:
    image: hashicorp/vault:1.17
    container_name: vault
    cap_add:
      - IPC_LOCK  # Lock memory to prevent sensitive data from swapping to disk
    environment:
      VAULT_DEV_ROOT_TOKEN_ID: keytouse  # Root token for dev mode
      VAULT_ADDR: http://0.0.0.0:8200  # Set the Vault address
    ports:
      - "8200:8200"  # Expose Vault on port 8200
    command: server -dev  # Start Vault in development mode
    volumes:
      - vault-data:/vault/file  # For future use, persistent storage in production
    healthcheck:
      test: ["CMD", "vault", "status"]
      interval: 30s
      timeout: 10s
      retries: 5
volumes:
  vault-data:

Access it with http://homelab:8200

IT-Tools

Ever wanted a site that has all the things a Software engineer needs daily?

  • Decoding JWT
  • Generate random strings
  • Create public/private keys
services:
    it-tools:
        image: 'corentinth/it-tools:latest'
        ports:
            - '2546:80'
        container_name: it-tools

http://homelab:2546

Postgres

The open source and undefeted database that can do mostly everything

services:
  db:
    image: postgres:16-alpine
    restart: always
    environment:
      - POSTGRES_USER=myadminuser
      - POSTGRES_PASSWORD=mySuperSecureAdminPassword@#!
    ports:
      - 5432:5432
    volumes:
      - ./data:/var/lib/postgresql/data

No UI but you can use pgAdmin

services:
  pgadmin:
    image: dpage/pgadmin4:latest
    environment:
      PGADMIN_DEFAULT_EMAIL: "madeupemail@server.com"
      PGADMIN_DEFAULT_PASSWORD: "mySuperSecurePGAdminPassword9*&%"
    ports:
        - "8095:80"

http://homelab:8095

MQ-Rabbit

Open source Queuing, more powerful than azure storage queues, it supports also topics

services:
  rabbitmq:
    image: rabbitmq:management
    container_name: rabbitmq
    environment:
      - RABBITMQ_DEFAULT_USER=mymquser
      - RABBITMQ_DEFAULT_PASS=rabbitmqpass@#$
    ports:
      - "5672:5672"
      - "15672:15672"

networks:
  default:
    driver: bridge

http://homelab:15672

Redis

Fast cache and queuing

services:
  cache:
    image: redis:6.2-alpine
    restart: always
    ports:
      - '6379:6379'
    command: redis-server --save 20 1 --loglevel warning --requirepass myredispassword&*^
    volumes: 
      - cache:/data
volumes:
  cache:
    driver: local

No UI but you can use

GitHub - qishibo/AnotherRedisDesktopManager: 🚀🚀🚀A faster, better and more stable Redis desktop manager [GUI client], compatible with Linux, Windows, Mac.
🚀🚀🚀A faster, better and more stable Redis desktop manager [GUI client], compatible with Linux, Windows, Mac. - qishibo/AnotherRedisDesktopManager

SonarQube

Yes, there is a community version that you can use for simple code analysis

services:
  sonarqube:
    image: sonarqube:community
    hostname: sonarqube
    container_name: sonarqube
    read_only: true
    depends_on:
      db:
        condition: service_healthy
    environment:
      SONAR_JDBC_URL: jdbc:postgresql://db:5432/sonar
      SONAR_JDBC_USERNAME: sonar
      SONAR_JDBC_PASSWORD: sonar
    volumes:
      - sonarqube_data:/opt/sonarqube/data
      - sonarqube_extensions:/opt/sonarqube/extensions
      - sonarqube_logs:/opt/sonarqube/logs
      - sonarqube_temp:/opt/sonarqube/temp
    ports:
      - "9900:9000"
    networks:
      - ${NETWORK_TYPE:-ipv4}
  db:
    image: postgres:17
    healthcheck:
      test: ["CMD-SHELL", "pg_isready"]
      interval: 10s
      timeout: 5s
      retries: 5
    hostname: postgresql
    container_name: postgresql
    environment:
      POSTGRES_USER: sonar
      POSTGRES_PASSWORD: sonar
      POSTGRES_DB: sonar
    volumes:
      - postgresql:/var/lib/postgresql
      - postgresql_data:/var/lib/postgresql/data
    networks:
      - ${NETWORK_TYPE:-ipv4}

volumes:
  sonarqube_data:
  sonarqube_temp:
  sonarqube_extensions:
  sonarqube_logs:
  postgresql:
  postgresql_data:

networks:
  ipv4:
    driver: bridge
    enable_ipv6: false
  dual:
    driver: bridge
    enable_ipv6: true
    ipam:
      config:
        - subnet: "192.168.2.0/24"
          gateway: "192.168.2.1"
        - subnet: "2001:db8:2::/64"
          gateway: "2001:db8:2::1"

http://homelab:9900

These are my must execute containers, I have many more, but they are more related to specific projects.

Next time I will explain how to use the container SonarQube to analyze C# projects.

Happy coding!!!