Skip to content

Setting Up Pydio Cells on Raspberry Pi 5/4/3 (Linux ARM64)

This documentation provides a comprehensive guide to setting up Pydio Cells on your Raspberry Pi 5 with the Linux ARM64 file. It includes instructions for preparing the environment, downloading and installing Pydio Cells, configuring storage, setting up SSL, and automating services.

/ ARTICLE
[ FIG. 1 ]
Free Server for Personal Use

Dropbox raised its prices again and I got tired of it. That’s the honest origin story for most self-hosted file servers, and it’s a fine reason — but the better one is that your files stop being subject to somebody else’s terms of service, retention policy, or scanning.

A Pi 5 handles this workload comfortably. The 8 GB model with a proper external disk will saturate gigabit for a handful of concurrent users, which is more than a household or small team needs. Pydio Cells is the software half: open source, genuinely mature, and — unlike a lot of self-hosted tooling — it doesn’t look like it was designed in 2009.

We’re doing it in Docker, on 64-bit ARM, with TLS and a real domain from the start. That last part isn’t optional decoration. A file server reachable over plain HTTP is worse than no file server, because you’ve now built a convenient central place for someone to harvest your credentials and everything you own.

Prerequisites

Four things before you start:

  1. A Raspberry Pi 5 (8GB RAM recommended) running a 64-bit OS — either Raspberry Pi OS Lite (64-bit) or Ubuntu Server 24.04 LTS for ARM64.
  2. One or two external HDDs for storage (e.g., 1 TB and 2 TB).
  3. A Cloudflare account for DNS management.
  4. A registered domain name (e.g., example.com).

Setting Up Pydio Cells with Docker on Raspberry Pi 5 (Linux ARM64)

OS prep through to TLS-secured remote access. Docker earns its place here for three reasons: the services stay isolated from the host, upgrading Cells is a pull-and-restart instead of an afternoon, and when you inevitably break something you tear down the stack and start over in two minutes.


Step 1: Prepare the Environment

Patch first. A Pi image pulled from a mirror last month is already behind, and there’s no point hardening a stack that sits on stale packages.

  1. Update your system:

    sudo apt update && sudo apt upgrade -y
    
  2. Install essential utilities:

    sudo apt install curl wget unzip -y
    
  3. Confirm you’re on a 64-bit kernel: Pydio Cells requires a 64-bit architecture. Verify with:

    uname -m
    

    aarch64 and you’re fine. armv7l means you flashed a 32-bit image, and there’s no upgrade path — reflash. Better to find out now than after you’ve configured everything and watched the Cells container refuse to start.

  4. Install Docker: Use the official install script, which handles the correct package for your architecture:

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

    Add yourself to the docker group to skip sudo on every command. Understand what you’re doing, though: group membership is root. Anyone who can run docker can mount the host filesystem into a container and read or write anything. So this is only acceptable if the account is protected by key-based SSH with password auth disabled.

    sudo usermod -aG docker $USER
    

    Log out and back in for the group change to take effect.

  5. Verify Docker is working:

    docker run --rm hello-world
    

Step 2: Securely Prepare Storage (External HDDs)

EXT4 with restrictive mount options. Nothing exotic — the goal is that a file someone uploads can’t become a file someone executes.

  1. Format the drives: Identify them first, and read the output carefully. mkfs.ext4 against the wrong device is instant, silent, and unrecoverable:

    lsblk
    

    Then format — replace sdX with your actual drive:

    sudo mkfs.ext4 /dev/sdX
    
  2. Create mount points:

    sudo mkdir -p /mnt/hdd1 /mnt/hdd2
    
Advertisement
  1. Configure auto-mount on boot: Mount by UUID, never by /dev/sdX. Device letters are assigned in enumeration order, so unplugging a drive or a slow-spinning USB disk can swap sda and sdb across a reboot — and now your data volume is mounted where your backup volume should be.

    Get your UUIDs:

    sudo blkid
    

    Edit the filesystem table:

    sudo nano /etc/fstab
    

    Add your entries:

    UUID=3fe457c5-0892-44ef-9f8e-5753f49e0cba /mnt/hdd1 ext4 defaults,noexec 0 0
    UUID=80fabc25-61d6-4448-ba0b-9a627ec348b4 /mnt/hdd2 ext4 defaults,noexec 0 0
    

    noexec is the flag doing the work. Anything on that mount can’t be executed directly, which closes the simplest path from “attacker uploaded a file” to “attacker is running code.” It’s not a complete defence — an interpreter will happily read a script from a noexec mount — but it costs nothing and removes the lazy option.

    Worth adding nosuid and nodev while you’re there, for the same reason: zero cost, and they eliminate two more categories of stupid.

    Apply the changes:

    sudo mount -a
    
  2. Verify and set ownership:

    df -h
    sudo chown -R $USER:$USER /mnt/hdd1 /mnt/hdd2
    sudo chmod -R 750 /mnt/hdd1 /mnt/hdd2
    

Step 3: Configure Docker for Pydio Cells

One docker-compose.yml describes the whole stack. Keep this file in version control — when the Pi’s SD card dies eighteen months from now, this file plus your data volumes is the entire rebuild.

  1. Create the project directory:

    mkdir ~/pydio-cells && cd ~/pydio-cells
    nano docker-compose.yml
    
  2. Paste in the configuration: Generate the passwords, don’t invent them. openssl rand -base64 24 twice, paste the output, move on. Nobody types these — they live in the compose file and your password manager, so length costs you nothing.

    And note image: pydio/cells:latest. Convenient for a first build, mildly reckless afterwards: a docker compose pull can hand you a major version bump you weren’t expecting. Once this is running and you care about it, pin the tag.

    services:
      mysql:
        image: mysql:8
        restart: unless-stopped
        environment:
          MYSQL_ROOT_PASSWORD: your_strong_root_passwd
          MYSQL_DATABASE: pydio_db
          MYSQL_USER: pydio_user
          MYSQL_PASSWORD: your_strong_db_passwd
        volumes:
          - mysqldir:/var/lib/mysql
    
      cells:
        image: pydio/cells:latest
        restart: unless-stopped
        ports:
          - "8080:8080"
        environment:
          CELLS_WORKING_DIR: "/var/cells"
          CELLS_SITE_BIND: ":8080"
          CELLS_SITE_EXTERNAL: "https://dir.example.com"
        volumes:
          - cellsdir:/var/cells
          - /mnt/hdd1/pydio_data:/var/cells/data
        depends_on:
          - mysql
    
    volumes:
      mysqldir:
      cellsdir:
    
  3. Start the stack:

    docker compose up -d
    
  4. Check that containers are running:

    docker ps
    

Step 4: Initialize Pydio Cells

  1. Open a browser and go to http://<raspberry-pi-ip>:8080.

  2. Work through the wizard:

    • MySQL credentials — pydio_user and the password from your compose file.
    • Admin account. Don’t call it admin. Every credential-stuffing bot on the internet tries that name first, and picking something else means the noise never turns into a real attempt. Long passphrase, not a clever password.
    • Storage path /var/cells/data, which is your /mnt/hdd1/pydio_data on the host side of the bind mount.

    Do this step on the LAN, before the service is reachable from outside. An initialisation wizard exposed to the internet is a free admin account for whoever finds it first.


Advertisement

Step 5: Dynamic DNS with Cloudflare

Residential connections mostly hand you a dynamic IP, and yours will change without warning — usually at 3 a.m., usually while you’re away. Dynamic DNS keeps the record pointing at the right address. Cloudflare does this well, and the orange-cloud proxy has a side benefit worth having: your home IP stops being published in public DNS.

  1. Install ddclient:

    sudo apt install ddclient -y
    
  2. Configure ddclient for Cloudflare:

    sudo nano /etc/ddclient.conf
    

    Scoped API Token, not the Global API Key. This matters more than it sounds: the Global Key controls your entire Cloudflare account, and it’s about to sit in a plaintext file on a Pi in your living room. Mint a token with Zone:DNS:Edit on that one zone and nothing else. If the Pi gets popped, the blast radius is one DNS record.

    protocol=cloudflare
    use=web
    login=token
    password=your_cloudflare_api_token
    zone=example.com
    dir.example.com
    
  3. Lock down the config file — it contains your API token:

    sudo chmod 600 /etc/ddclient.conf
    sudo chown root:root /etc/ddclient.conf
    
  4. Enable and start the service:

    sudo systemctl enable ddclient
    sudo systemctl start ddclient
    

Step 6: Enforce Encryption with Let’s Encrypt

Everything so far assumes HTTPS. Over plain HTTP, your admin password and every file you touch cross the network in cleartext — readable by your ISP, by anyone on the coffee shop Wi-Fi you’re connecting from, by whatever’s between. Let’s Encrypt makes this free, so there is no argument left for skipping it.

  1. Install Certbot:

    sudo apt install certbot -y
    
  2. Issue the certificate: Port 80 has to reach the Pi for the HTTP-01 challenge, so forward it on the router first. Cloudflare proxy on? Turn it off temporarily, or Let’s Encrypt validates against Cloudflare’s edge instead of your box and the challenge fails with a confusing error.

    sudo certbot certonly --standalone -d dir.example.com
    
  3. Mount the certificates into the Docker container: Edit your docker-compose.yml and update the cells volumes section:

      cells:
        # ... previous configuration ...
        volumes:
          - cellsdir:/var/cells
          - /mnt/hdd1/pydio_data:/var/cells/data
          - /etc/letsencrypt/live/dir.example.com:/etc/ssl/certs:ro
    

    :ro mounts them read-only. The container needs to read a private key; it has no reason to be able to overwrite one. Restart to pick it up:

    docker compose down
    docker compose up -d
    
  4. Automate certificate renewal: Ninety-day lifetimes. That’s short by design, and it means an unautomated renewal will expire while you’re on holiday. Cron it:

    sudo crontab -e
    

    This runs daily at 02:00. Certbot no-ops unless the cert is inside its renewal window, and the --deploy-hook only fires when a new certificate actually lands — so the container restarts roughly once a quarter, not every night:

    0 2 * * * certbot renew --quiet --deploy-hook "cd /home/pi/pydio-cells && docker compose restart cells"
    

    Replace /home/pi/pydio-cells with the actual path to your project directory.


Step 7: Final Security Hardening

Three things left.

  1. Firewall: ufw, allowing only what you need. Note that 8080 in this ruleset is the direct Cells port — useful while you’re testing on the LAN, and something you should drop once traffic goes through 443 properly. Leaving it open means anyone can bypass your TLS termination and hit the app directly:

    sudo ufw allow 22/tcp
    sudo ufw allow 80/tcp
    sudo ufw allow 443/tcp
    sudo ufw allow 8080/tcp
    sudo ufw enable
    
  2. Access Control in Pydio Cells: Log in at https://dir.example.com and turn on 2FA for every account, starting with yours. Set a minimum password policy. Then go through the share settings — Cells will happily generate public links, which is a feature until someone shares a folder they didn’t mean to.

  3. Verify: Padlock in the browser, and dig dir.example.com returning what you expect. If you left the Cloudflare proxy on you’ll see Cloudflare’s IPs rather than your own, which is correct — that’s the point of it.

Done. Encrypted in transit, authenticated, firewalled, and running on hardware in your house.

One last thing, and it’s the thing people skip: this is a file server, not a backup. RAID isn’t a backup either. A single Pi with a single external disk is one drive failure away from losing everything on it, and you’ve now consolidated your data in one place, which makes that failure worse than it used to be. Set up a second copy somewhere else — restic to cheap object storage runs a few dollars a month — and test a restore before you need one.


Share article

Subscribe to my newsletter

Receive my case study and the latest articles on my WhatsApp Channel.

Warning

Ask CyberROX AI