Third party cookies may be stored when visiting this site. Please see the cookie information.

PenguinTutor YouTube Channel

Raspberry Pi CM4 Home Server Guide

Part 2: NAS, Docker, Monitoring, and Web Dashboard

Welcome to the companion guide for Part 2 of the CM4 Home Server series. Below you will find the configuration files and commands used in the video to set up your NVMe-powered server.

Note: If you haven't configured your NVMe boot yet, check out Part 1 to set your EEPROM boot and install the Operating system

Samba Configuration (NAS using Windows Share / SMBD protocol)

Install Samba using:


    sudo apt install samba samba-common-bin

If you do want to use password authentication then you should used the smbpasswd command. If you are using guest access you can just add the following.

Append the following to /etc/samba/smb.conf for guest-accessible, read-only shares.

[tv-film]

path = /extdata/tv-film

browseable = yes

read only = yes

guest ok = yes

force user = nobody



[music]

path = /extdata/music

browseable = yes

read only = yes

guest ok = yes

force user = nobody

Installing Docker

The Docker website provides a handy script which can be used to install Docker and it's dependencies.



curl -fsSL https://get.docker.com -o get-docker.sh

sudo sh get-docker.sh

After installing you can add permission using:


sudo usermod -aG docker $USER

Test it is setup correctly using:


docker run hello-world

Installing Portainer for Docker Management

Portainer provides a web interface which makes managing docker containers a breeze. Install using.


mkdir -p ~/docker_data/portainer

docker run -d -p 8000:8000 -p 9443:9443 --name portainer \

    --restart=always \

    -v /var/run/docker.sock:/var/run/docker.sock \

    -v ~/docker_data/portainer:/data \

    portainer/portainer-ce:latest

To connect open a web browser and enter your IP address followed by port 9443. E.g.
http://homeserver.local:9443

You will likely get a warning about the use of a local TLS certificate which you can ignore for use on a local home network. If however you are setting it up for external access then you will want to register a certificate.

Login to portainer and setup a username and password as soon as possible to ensure that nobody is able to intercept the setup process!

Glances Monitoring (Docker Stack)

In Portainer, create a new stack named "monitor" and paste the following YAML configuration:

version: '3.8'

services:

  glances:

    image: nicolargo/glances:latest

    container_name: glances

    restart: always

    environment:

      - "GLANCES_OPT=-w"

    ports:

      - "61208:61208"

    volumes:

      - /var/run/docker.sock:/var/run/docker.sock:ro

      - /:/host:ro

    network_mode: host

    pid: host

Nginx & PHP Web Server

This stack sets up a lightweight web server to host your custom home dashboard. In the video I use a two step process, but you can setup both Nginx and PHP in one step. Create the file ~/webserver/nginx/default.conf

server {

    listen 80;

    server_name localhost;



    # This must match the folder path inside the container

    root /usr/share/nginx/html;

    index index.php index.html index.htm;



    location / {

        try_files $uri $uri/ =404;

    }



    # Pass PHP scripts to the PHP-FPM container

    location ~ \.php$ {

        fastcgi_pass php:9000;

        fastcgi_index index.php;

        include fastcgi_params;

        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;

    }

}

Then using portainer choose "Add Stack" and use the following YAML config.

services:

  web:

    image: nginx:latest

    container_name: nginx_server

    restart: unless-stopped

    ports:

      - "80:80"

    volumes:

      - /home/$USER/webserver/html:/usr/share/nginx/html:ro

      # Mount the custom config file we just made

      - /home/$USER/webserver/nginx/default.conf:/etc/nginx/conf.d/default.conf:ro

    depends_on:

      - php



  php:

    image: php:fpm

    container_name: php_server

    restart: unless-stopped

    volumes:

      # PHP needs access to the exact same files as Nginx to process them

      - /home/$USER/webserver/html:/usr/share/nginx/html

      

Remember to replace ${USER} with your actual username if not using environment variables.

Using Gemini AI to generate the web page

Whilst I usually create my web pages by hand in this case I used Gemini to create the page for me. This is the prompt I used:



Please create a dynamic web page powered by PHP. 



The page should create large click through images which can be used to access local resources on my home server. This should look similar to how a media server provides large buttons for the media.



Incorporate a background image “background.png” which should be scaled to completely fill the page but maintain the image aspect ratio.



Have a clear title in a large font across the top of the page. This should say “Home Server”



The main part of the page should then be a grid view displaying links to the different services. Each will have a thumbnail image. The services should be loaded from a json formatted config file services.json



The json file is structured as a list of entries. Each entry has a “name” - which should be used as the title when hovering over the image, a “url” which is accessed when clicking on the image and an “image” which provides the thumbnail image. The images should be taken from the image directory.  



Please update the background.png location to the images directory.

Please can you set all the other images to 1:1.

This is the PHP web page that it created.



<?php

// Load services from the JSON file

$services_json = file_get_contents('services.json');

$services = json_decode($services_json, true);

?>



<!DOCTYPE html>

<html lang="en">

<head>

    <meta charset="UTF-8">

    <meta name="viewport" content="width=device-width, initial-scale=1.0">

    <title>Home Server Dashboard</title>

    <style>

        body {

            margin: 0;

            padding: 0;

            font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;

            color: white;

            background: url('images/background.png') no-repeat center center fixed;

            background-size: cover;

            min-height: 100vh;

        }



        .overlay {

            background: rgba(0, 0, 0, 0.4);

            min-height: 100vh;

            padding: 60px 20px;

            box-sizing: border-box;

        }



        h1 {

            text-align: center;

            font-size: clamp(2.5rem, 8vw, 4.5rem);

            margin-bottom: 60px;

            letter-spacing: 2px;

            text-shadow: 0 4px 10px rgba(0,0,0,0.8);

        }



        .grid-container {

            display: grid;

            grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));

            gap: 40px;

            max-width: 1400px;

            margin: 0 auto;

        }



        .service-card {

            position: relative;

            aspect-ratio: 1 / 1; 

            overflow: hidden;

            border-radius: 20px;

            box-shadow: 0 10px 30px rgba(0,0,0,0.6);

            transition: all 0.3s cubic-bezier(0.25, 0.8, 0.25, 1);

            background: rgba(255, 255, 255, 0.1);

            backdrop-filter: blur(5px);

        }



        .service-card:hover {

            transform: translateY(-10px) scale(1.03);

            box-shadow: 0 20px 40px rgba(0,0,0,0.8);

            outline: 4px solid rgba(255, 255, 255, 0.5);

        }



        .service-card img {

            width: 100%;

            height: 100%;

            object-fit: cover;

            display: block;

        }



        @media (max-width: 600px) {

            .grid-container {

                grid-template-columns: repeat(auto-fill, minmax(150px, 1fr));

                gap: 20px;

            }

        }

    </style>

</head>

<body>



<div class="overlay">

    <h1>Home Server</h1>



    <div class="grid-container">

        <?php if ($services): ?>

            <?php foreach ($services as $service): ?>

                <div class="service-card">

                    <a href="<?php echo htmlspecialchars($service['url']); ?>" 

                       title="<?php echo htmlspecialchars($service['name']); ?>">

                        <img src="images/<?php echo htmlspecialchars($service['image']); ?>" 

                             alt="<?php echo htmlspecialchars($service['name']); ?>">

                    </a>

                </div>

            <?php endforeach; ?>

        <?php else: ?>

            <p style="text-align:center;">No services found. Check your services.json file.</p>

        <?php endif; ?>

    </div>

</div>



</body>

</html>

Next Steps

In future I'll be adding details of how to install the JellyFin media server to provide a easy to use server for videos and music. Please Subscribe to the PenguinTutor YouTube Channel to get notified of future updates.

Previous Using a Raspberry Pi compute module for a server with NVMe
Using a Raspberry Pi compute module for a server with NVMe
Next Running the Raspberry Pi headless with Debian Linux
Running the Raspberry Pi headless with Debian Linux