Post

Homelab Part 8 — Nextcloud Personal Cloud Storage

Deploying Nextcloud on Kubernetes with a MariaDB database and Longhorn storage — giving myself a self-hosted Google Drive accessible from any device on the network.

Homelab Part 8 — Nextcloud Personal Cloud Storage

What Nextcloud Is

Nextcloud is a self-hosted cloud storage platform — like Google Drive or iCloud, but running entirely on my hardware. I can:

  • Sync files between devices automatically (phone, laptop, iPad)
  • Access files via a browser or the Nextcloud mobile app
  • Store photos, documents, notes — anything
  • Share files with links (within my network, or via Cloudflare Tunnel later)
  • Add apps inside Nextcloud: calendar, contacts, tasks, notes

The data never leaves my home network. No cloud company has a copy.


What Nextcloud Needs

Nextcloud requires more than just a container — it needs a database. Nextcloud stores all its metadata (file names, user accounts, sharing permissions, settings) in a relational database. I’m using MariaDB.

So I’m deploying two pods:

  1. MariaDB — the database backend
  2. Nextcloud — the application itself

Both get Longhorn volumes for their data.


The Storage Plan

ResourceSizeWhat It Stores
MariaDB PVC10GiThe database (file metadata, users, sharing)
Nextcloud Data PVC50GiThe actual files uploaded to Nextcloud

50GB is my starting point. Longhorn volumes can be expanded later without downtime.


Deploying MariaDB

I create the database first, then Nextcloud connects to it.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# mariadb.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: mariadb-data
  namespace: cloud
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 10Gi
---
apiVersion: v1
kind: Secret
metadata:
  name: mariadb-secret
  namespace: cloud
type: Opaque
stringData:
  MYSQL_ROOT_PASSWORD: "your-root-password-here"
  MYSQL_DATABASE: nextcloud
  MYSQL_USER: nextcloud
  MYSQL_PASSWORD: "your-nextcloud-db-password-here"
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: mariadb
  namespace: cloud
spec:
  replicas: 1
  selector:
    matchLabels:
      app: mariadb
  template:
    metadata:
      labels:
        app: mariadb
    spec:
      containers:
        - name: mariadb
          image: mariadb:10.11
          envFrom:
            - secretRef:
                name: mariadb-secret
          volumeMounts:
            - name: data
              mountPath: /var/lib/mysql
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: mariadb-data
---
apiVersion: v1
kind: Service
metadata:
  name: mariadb
  namespace: cloud
spec:
  selector:
    app: mariadb
  ports:
    - port: 3306
      targetPort: 3306

Replace your-root-password-here and your-nextcloud-db-password-here with actual strong passwords before applying.

1
2
3
kubectl apply -f mariadb.yaml
kubectl get pods -n cloud
# mariadb-xxx    1/1   Running

[SCREENSHOT]kubectl get pods -n cloud showing mariadb pod Running


Deploying Nextcloud

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
# nextcloud.yaml
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
  name: nextcloud-data
  namespace: cloud
spec:
  accessModes:
    - ReadWriteOnce
  resources:
    requests:
      storage: 50Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
  name: nextcloud
  namespace: cloud
spec:
  replicas: 1
  selector:
    matchLabels:
      app: nextcloud
  template:
    metadata:
      labels:
        app: nextcloud
    spec:
      containers:
        - name: nextcloud
          image: nextcloud:28-apache
          ports:
            - containerPort: 80
          env:
            - name: NEXTCLOUD_ADMIN_USER
              value: admin
            - name: NEXTCLOUD_ADMIN_PASSWORD
              value: "your-admin-password-here"
            - name: NEXTCLOUD_TRUSTED_DOMAINS
              value: nextcloud.home.lab
            - name: MYSQL_HOST
              value: mariadb
            - name: MYSQL_DATABASE
              value: nextcloud
            - name: MYSQL_USER
              value: nextcloud
            - name: MYSQL_PASSWORD
              valueFrom:
                secretKeyRef:
                  name: mariadb-secret
                  key: MYSQL_PASSWORD
          volumeMounts:
            - name: data
              mountPath: /var/www/html
      volumes:
        - name: data
          persistentVolumeClaim:
            claimName: nextcloud-data
---
apiVersion: v1
kind: Service
metadata:
  name: nextcloud
  namespace: cloud
spec:
  selector:
    app: nextcloud
  ports:
    - port: 80
      targetPort: 80
---
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  name: nextcloud
  namespace: cloud
  annotations:
    cert-manager.io/cluster-issuer: homelab-ca-issuer
    nginx.ingress.kubernetes.io/proxy-body-size: "0"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "600"
    nginx.ingress.kubernetes.io/proxy-buffering: "off"
spec:
  ingressClassName: nginx
  tls:
    - hosts:
        - nextcloud.home.lab
      secretName: nextcloud-tls
  rules:
    - host: nextcloud.home.lab
      http:
        paths:
          - path: /
            pathType: Prefix
            backend:
              service:
                name: nextcloud
                port:
                  number: 80
1
kubectl apply -f nextcloud.yaml

Watch the pod start — Nextcloud takes 1–2 minutes on first boot because it initialises the database:

1
2
3
kubectl get pods -n cloud -w
# nextcloud-xxx    0/1   ContainerCreating ...
# nextcloud-xxx    1/1   Running           ...

[SCREENSHOT]kubectl get pods -n cloud showing both mariadb and nextcloud pods Running


First Login

Open https://nextcloud.home.lab in a browser.

Log in with:

  • Username: admin
  • Password: whatever you set in NEXTCLOUD_ADMIN_PASSWORD

[SCREENSHOT]Nextcloud login screen at nextcloud.home.lab

Nextcloud shows a welcome screen and offers to install recommended apps. I install:

  • Files (always on)
  • Photos
  • Calendar
  • Contacts

[SCREENSHOT]Nextcloud files view — the main dashboard after login


Creating User Accounts

The admin account is for administration. I create a regular user for daily use:

Top-right menu → Administration → Users → New user:

  • Username: muhammed
  • Password: set a strong one
  • Quota: unlimited (or set a limit if you share with family)

[SCREENSHOT]Nextcloud user management screen showing the new user created


Setting Up File Sync on Devices

Windows/Mac — Nextcloud Desktop Client

Download from nextcloud.com/install/#install-clients and install.

Set up:

  • Server address: https://nextcloud.home.lab
  • Login with the user account (not admin)
  • Choose which folders to sync

Files in the sync folder appear in Windows Explorer automatically.

[SCREENSHOT]Nextcloud desktop client showing sync status and file list

iPhone/iPad — Nextcloud Mobile App

Download from the App Store.

Setup:

  • Add account → Server: https://nextcloud.home.lab
  • Log in

The app syncs photos automatically if you enable camera upload. All files are accessible offline if you mark them as favorites.

[SCREENSHOT]Nextcloud mobile app showing files and camera upload settings


Fixing the Nextcloud Warnings

After the first login, Nextcloud’s admin panel often shows warnings. Common ones and how to fix them:

Warning: Trusted Domain Not Set

If Nextcloud shows “You are accessing the server from an untrusted domain”:

1
2
3
# Run occ command inside the pod
kubectl exec -n cloud deployment/nextcloud -- \
  php occ config:system:set trusted_domains 0 --value="nextcloud.home.lab"

This is already handled by the NEXTCLOUD_TRUSTED_DOMAINS env var, but if it still appears, the occ command above fixes it permanently.

Warning: Security & Setup Warnings

In the admin panel (Administration → Overview), Nextcloud checks for configuration issues. Most can be resolved:

1
2
3
# Fix missing background jobs (use system cron instead of AJAX)
kubectl exec -n cloud deployment/nextcloud -- \
  php occ background:cron

For the memory cache warning, add to Nextcloud’s config:

1
2
kubectl exec -n cloud deployment/nextcloud -- \
  php occ config:system:set memcache.local --value="\\OC\\Memcache\\APCu"

[SCREENSHOT]Nextcloud admin overview showing green checkmarks — no critical warnings


Uploading and Accessing Files

The Nextcloud web interface is straightforward — drag and drop files in the browser, create folders, and share links.

The real power is the desktop sync client. Once configured, any file I put in the Nextcloud folder on my laptop is available instantly on my phone and iPad. Documents, PDFs, photos — all synced automatically, no manual steps.


Nextcloud vs Google Drive — Key Differences

FeatureGoogle DriveNextcloud
Storage cost15GB free, then paidLimited by your disk
PrivacyGoogle sees your filesOnly you see your files
Offline accessLimitedFull (with sync)
Custom appsNoCalendar, Contacts, Notes, etc.
External accessAlways onNeeds Cloudflare Tunnel (Part 9)
Setup complexityZeroWhat we just did

The trade-off is real — Google Drive just works everywhere from the start. Nextcloud takes setup, but what I build here I own completely.


Where I Am Now

At the end of Part 8 I have:

  • ✅ MariaDB running in the cloud namespace with a Longhorn PVC
  • ✅ Nextcloud running and connected to MariaDB
  • ✅ File storage with 50GB Longhorn volume (expandable)
  • ✅ HTTPS access at https://nextcloud.home.lab
  • ✅ Desktop sync client configured on Windows
  • ✅ Mobile app connected on iPhone/iPad

Both Jellyfin and Nextcloud are now running. The homelab is functional on the home network.

Next: Cloudflare Tunnel — the optional last piece, for accessing Jellyfin and Nextcloud securely from anywhere outside the home network.


You can find me online at:

My signature image

This post is licensed under CC BY 4.0 by the author.