Self-Hosting services at home under Docker
Building a Self-Hosted Server on a Mac Mini with Docker
Mac Mini · Docker Desktop · Docker Compose · Nginx Proxy Manager · Self-Hosted Services
A modern Mac Mini makes an excellent small self-hosted server.
It is quiet, compact, energy efficient and powerful enough to run a surprisingly large collection of services simultaneously. Combined with Docker, it is possible to build a complete personal server environment without dedicating a room to conventional server hardware.
This is the foundation of my self-hosted environment.
The Mac Mini runs Docker Desktop, with each service isolated in its own Docker container. Nginx Proxy Manager provides the common HTTPS gateway, while a shared Docker network allows the containers to communicate directly without exposing unnecessary ports to the host or the internet.
The result is a small private cloud providing file storage, photography, documents, books, passwords, monitoring, collaboration, email relay and a personal blog — all running on one machine.
This article describes the initial Docker installation and configuration. The individual services are covered in the linked articles below.
The Overall Architecture
The basic architecture looks like this:
INTERNET
│
Cloudflare DNS
│
Router / Firewall
Ports 80/443
│
▼
┌─────────────────────────┐
│ Nginx Proxy Manager │
│ HTTPS Gateway │
│ │
│ Let's Encrypt SSL/TLS │
└────────────┬────────────┘
│
proxy-net
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
Nextcloud Immich Ghost
Collabora Immich Kiosk Blog Server
│ │ │
└───────────────┬───┴───────────────────┘
│
Other Docker Services
│
┌───────────────┼────────────────────┐
▼ ▼ ▼
Vaultwarden Paperless Homebox
Passwords Documents Inventory
┌───────────────┼────────────────────┐
▼ ▼ ▼
Calibre Uptime Kuma Mail Relay
Book Library Monitoring Postfix → Brevo
The important principle is that Docker provides the isolation and service management, while Nginx Proxy Manager provides the common external gateway.
Most containers do not need a host port at all. They communicate over Docker's internal network and are reached externally through Nginx Proxy Manager.
Part 1 — Installing Docker on the Mac Mini
Stage 1 — Install Docker Desktop
The simplest approach on macOS is Docker Desktop.
Download and install Docker Desktop for Mac, selecting the Apple Silicon version for an M-series Mac.
After installation, start Docker Desktop and allow it to complete its initial configuration.
Verify that Docker is working:
docker version
You should see both a Client and Server section.
Then check Docker Compose:
docker compose version
Modern Docker Desktop includes Docker Compose, so there is normally no need to install Compose separately.
Stage 2 — Configure Docker Desktop
Before installing any applications, configure Docker Desktop appropriately for a server that will run continuously.
Open:
Docker Desktop → Settings
The exact settings vary slightly between Docker Desktop releases, but the important considerations are:
Resources
Allocate enough CPU and memory for the services you intend to run.
Do not simply allocate everything available to Docker. macOS still needs resources for itself and for applications such as Terminal, Finder, browsers and backup software.
The correct allocation depends on the number and type of containers being run.
Services such as Immich, Nextcloud and Collabora can consume considerably more resources than lightweight services such as Uptime Kuma or Vaultwarden.
Start Docker Desktop at Login
Enable Docker Desktop to start automatically when the Mac starts.
This is particularly important if the Mac is being used as a server rather than as a conventional desktop computer.
However, starting Docker is not the same as starting the applications in the correct order. That is dealt with later using a startup script.
Part 2 — Create the Docker Directory Structure
I keep the Docker configuration for each application in its own directory.
For example:
~/docker/
├── mailrelay/
├── npm/
├── uptimekuma/
├── ghost/
├── nextcloud/
├── collabora/
├── immich/
├── immich-kiosk/
├── vaultwarden/
├── homebox/
├── calibre/
└── paperless/
Create the top-level directory:
mkdir -p ~/docker
Each application then gets its own directory:
mkdir -p ~/docker/mailrelay
mkdir -p ~/docker/npm
mkdir -p ~/docker/uptimekuma
mkdir -p ~/docker/ghost
mkdir -p ~/docker/nextcloud
mkdir -p ~/docker/collabora
mkdir -p ~/docker/immich
mkdir -p ~/docker/immich-kiosk
mkdir -p ~/docker/vaultwarden
mkdir -p ~/docker/homebox
mkdir -p ~/docker/calibre
mkdir -p ~/docker/paperless
Keeping applications separate makes updates, backups and troubleshooting considerably easier.
Part 3 — Understand Docker Compose
Each service is normally described by a docker-compose.yml file.
For example:
services:
example:
image: example/image:latest
container_name: example
restart: unless-stopped
The Compose file defines:
- the Docker image
- the container name
- persistent storage
- environment variables
- networks
- dependencies
- health checks
- restart behaviour
- exposed ports
Starting an application is then normally as simple as:
cd ~/docker/example
docker compose up -d
Checking it:
docker compose ps
Viewing its logs:
docker compose logs -f
Stopping it:
docker compose stop
These commands form the basic vocabulary for managing the entire server.
Part 4 — Create the Shared Docker Network
One of the most important decisions in this setup is to create a shared Docker network for services that need to communicate with one another.
Create it once:
docker network create proxy-net
Verify it:
docker network ls | grep proxy-net
The network is deliberately created outside the individual Compose projects.
Compose files therefore reference it as an external network:
networks:
proxy-net:
external: true
This is important.
The network is infrastructure shared by the applications rather than something owned by an individual application.
It also means that removing or recreating one application cannot accidentally destroy the network used by all the other services.
Part 5 — Why Container Names Matter
Once services are attached to proxy-net, they can communicate using their Docker container names.
For example:
nextcloud
collabora
mailrelay
vaultwarden
immich-kiosk
Nginx Proxy Manager can therefore forward traffic directly to:
nextcloud:80
collabora:9980
vaultwarden:80
mailrelay:25
immich-kiosk:3000
There is no need to discover the container's IP address.
Docker's internal DNS resolves the container name automatically.
This also means that application traffic can remain inside Docker rather than travelling through the Mac's host networking stack.
Part 6 — Persistent Storage
Containers themselves should generally be treated as disposable.
The important application data therefore needs to live outside the container.
For example:
volumes:
- ./config:/var/www/html/config
- ./data:/var/www/html/data
If the container is subsequently recreated, the data remains.
This distinction is fundamental:
Docker Container
│
├── Application
└── Temporary container filesystem
│
X
disposable
Persistent Volume / Bind Mount
│
└── Configuration and data
│
✓
retained
For large data collections, I use the external storage attached to the Mac rather than consuming the Mac's internal SSD.
This is particularly useful for photography, documents and other large collections.
Part 7 — Keep Secrets Out of Compose Files
Passwords, API keys and SMTP credentials should not normally be written directly into a Compose file.
Instead, use an .env file where appropriate:
nano .env
For example:
RELAYHOST_USERNAME=your_brevo_login
RELAYHOST_PASSWORD=your_brevo_smtp_key
Protect the file:
chmod 600 .env
The .env file should also be excluded from public repositories and treated as sensitive data.
I additionally store important credentials and recovery information in Vaultwarden.
Part 8 — The Role of Nginx Proxy Manager
Once Docker is working, Nginx Proxy Manager becomes the gateway to the externally accessible services.
Rather than exposing every container directly:
Internet
│
├── :80 → Nextcloud
├── :443 → Immich
├── :3001 → Uptime Kuma
├── :9980 → Collabora
└── etc.
the preferred architecture is:
Internet
│
▼
Router
│
:80/:443
│
▼
Nginx Proxy Manager
│
▼
proxy-net
│
├── Nextcloud
├── Immich
├── Ghost
├── Vaultwarden
└── other services
Only the gateway needs to be exposed to the internet.
Individual services can remain completely internal to Docker.
Nginx Proxy Manager also provides:
- HTTPS termination
- Let's Encrypt certificates
- HTTP → HTTPS redirection
- reverse proxying
- WebSocket support
- access restrictions
- request filtering
- a central place to manage external hostnames
The complete Nginx Proxy Manager installation and security configuration is covered separately in:
Making self-hosted services available and Secure under Docker
Part 9 — Starting the Services in the Correct Order
With multiple interconnected applications, startup order matters.
For example, Nextcloud depends on MariaDB and Redis.
Collabora depends on being able to communicate with Nextcloud.
The various applications may also depend on the shared proxy-net network.
Finally, Nginx Proxy Manager needs the upstream containers to exist and be attached to the network before it attempts to configure its proxies.
I therefore use a startup script which starts the services in a controlled order.
The basic principle is:
Docker Desktop
│
▼
proxy-net
│
▼
Supporting services
│
├── MariaDB
├── Redis
├── Mail Relay
└── other backends
│
▼
Application containers
│
├── Nextcloud
├── Immich
├── Ghost
├── Vaultwarden
└── etc.
│
▼
Nginx Proxy Manager
Nginx Proxy Manager should start last.
This avoids the common problem where NPM starts before one or more upstream containers have joined proxy-net, resulting in errors such as:
host not found in upstream
Part 10 — The Services
The following articles describe the individual services in detail.
Each title below will eventually link to its own installation, security and update guide.
Setting up a mail relay for self-hosted services under Docker
Postfix · Brevo · SMTP
A central SMTP relay for all the self-hosted applications.
Instead of configuring every application with its own external SMTP credentials, applications send mail internally to the Docker container at:
mailrelay:25
Postfix then authenticates with Brevo over encrypted SMTP and delivers the message to its destination.
This provides a simple centralised mail path for notifications from Ghost, Nextcloud, Vaultwarden, Paperless, Uptime Kuma and other applications.
The relay is deliberately not exposed to the internet.
Making self-hosted services available and Secure under Docker
Nginx Proxy Manager · Cloudflare DNS · Let's Encrypt · HTTPS
Nginx Proxy Manager is the front door to the Docker environment.
It maps public DNS names such as:
cloud.plainshawk.co.uk
photos.plainshawk.co.uk
blog.plainshawk.co.uk
to the appropriate internal Docker containers.
It also manages Let's Encrypt certificates and terminates HTTPS connections.
Security controls include restricting the NPM administration interface to the local network, enabling two-factor authentication, blocking common scanner traffic and restricting sensitive services to the home network where appropriate.
Monitoring the Docker Services
Uptime Kuma · Service monitoring · Availability alerts
Uptime Kuma provides a central dashboard showing whether the various services are available.
It can monitor HTTP/HTTPS endpoints, TCP ports and internal Docker services.
This provides an early warning when something fails rather than relying on discovering the problem when a service is needed.
Typical monitors include:
Nextcloud
Collabora
Immich
Ghost
Vaultwarden
Mail Relay
Nginx Proxy Manager
Setting up a Blog Server using Docker
Ghost · MySQL · Nginx Proxy Manager
Ghost provides the public-facing Plainshawk blog.
Running it in Docker keeps the blog application isolated from the rest of the server while persistent storage preserves the content and configuration.
Nginx Proxy Manager provides the public HTTPS endpoint, while the Ghost container itself remains inside the Docker environment.
The blog is available at:
https://blog.plainshawk.co.uk
Setting up an Office / Collaboration Space under Docker
Nextcloud · MariaDB · Redis · Collabora
Nextcloud provides the private cloud and collaboration platform.
It provides:
- file storage
- file synchronisation
- sharing
- calendars
- contacts
- collaborative applications
- web access
MariaDB provides the database and Redis provides caching and transactional file locking.
Collabora Online adds browser-based editing for:
- Word documents
- spreadsheets
- presentations
The result is effectively a small private office suite running entirely within the Docker environment.
Setting up an Image Gallery under Docker
Immich · PostgreSQL · Redis · Machine Learning
Immich provides the main personal photography platform.
It provides:
- photo and video storage
- automatic organisation
- albums
- searching
- facial recognition
- location information
- mobile applications
- sharing
- automatic uploads
The large photo library is stored on persistent external storage while the application and supporting services run in Docker.
Nginx Proxy Manager provides the external HTTPS endpoint.
Adding a rotating picture display under Docker
Immich Kiosk · Immich API · Full-screen slideshow
Immich Kiosk provides a dedicated display for photographs already stored in Immich.
Rather than duplicating the photo library, the kiosk connects directly to Immich over the internal Docker network.
This makes it possible to put an old tablet, computer or Raspberry Pi connected to a display into full-screen slideshow mode.
The kiosk can display:
- random photographs
- albums
- people
- favourites
- memories
- different layouts
- configurable slideshow transitions
It is particularly useful as a permanent digital photo frame.
Setting up a Password Store under Docker
Vaultwarden · Bitwarden-compatible password management
Vaultwarden provides a self-hosted password manager compatible with the Bitwarden ecosystem.
It stores passwords, secure notes, identities and other credentials in the private Docker environment.
It also provides an important role in managing the self-hosted infrastructure itself.
For example, I use it to store:
- Docker service credentials
- SMTP credentials
- API keys
- recovery codes
- administrator passwords
- other infrastructure secrets
Because Vaultwarden itself is a security-critical application, it receives additional access restrictions and should always be protected by strong authentication and HTTPS.
Setting up a Home Inventory under Docker
Homebox · Household inventory management
Homebox provides a searchable inventory of possessions around the home.
Items can be organised by:
- location
- category
- manufacturer
- model
- serial number
- purchase information
- warranty information
- notes
It is useful for maintaining an inventory of equipment, appliances, tools, electronics and other possessions.
Because the inventory is personal information, access is restricted to the home network.
Setting up a Book Library under Docker
Calibre · Calibre-Web · Digital library management
Calibre provides the underlying book library and ebook management environment.
Calibre-Web provides a convenient web interface for browsing and accessing the collection.
Together they provide:
- ebook storage
- metadata management
- authors and series
- searching
- browsing
- reading
- downloading
- library administration
The separation between the underlying Calibre library and the web interface also makes it possible to keep the library management environment relatively restricted.
Setting up a Document Store under Docker
Paperless-ngx · Document management · OCR
Paperless-ngx provides a searchable digital document archive.
Documents can be uploaded and automatically processed using OCR, making scanned documents searchable rather than simply storing them as images.
Documents can be organised using:
- correspondents
- document types
- tags
- dates
- custom fields
This makes Paperless particularly useful for maintaining a digital archive of household correspondence, receipts, manuals, certificates and other paperwork.
Part 11 — Security Principles
Running services at home does not automatically make them secure.
The most important principle is:
Do not expose a service to the internet unless there is a reason for it to be there.
The Docker environment therefore follows several basic rules.
1. Minimise exposed ports
Where possible, containers communicate through proxy-net rather than exposing ports on the Mac.
2. Use HTTPS externally
Publicly accessible services use Nginx Proxy Manager and Let's Encrypt.
3. Restrict administration interfaces
Administration interfaces should not normally be exposed publicly.
Where possible, they are restricted to the home network.
4. Use strong authentication
Passwords should be unique and stored in Vaultwarden.
Two-factor authentication should be enabled wherever it is available.
5. Keep credentials out of Compose files
Secrets belong in protected .env files or a suitable secrets-management system.
6. Keep Docker images updated
Containers are software, and software contains vulnerabilities.
Updates should therefore be performed regularly.
7. Back up persistent data
A Docker container can be recreated.
Your data cannot necessarily be recreated.
Backups therefore cover databases, application configuration, certificates and important persistent storage.
Part 12 — Updating the Environment
One of the advantages of this arrangement is that applications can be updated independently.
A typical update looks like:
cd ~/docker/example
docker compose pull
docker compose up -d --force-recreate
However, not every application should be updated in exactly the same way.
Some applications have databases and require migrations.
Some require major versions to be installed sequentially.
Some have no persistent database and can simply be recreated.
The individual service articles therefore document the appropriate update procedure for each application.
A particularly important rule is:
Always read the application's release notes before performing a major version upgrade.
Never assume that changing latest or pulling a new image is sufficient.
Part 13 — Backups
A self-hosted server is only as good as its backups.
The backup strategy covers both application data and configuration.
Typical backup targets include:
Docker configuration
Application configuration
Databases
Photo library
Document library
Book library
Nextcloud data
Nginx Proxy Manager configuration
Let's Encrypt certificates
For databases, a database-aware backup is preferable to simply copying a live database directory.
For example, MariaDB can be backed up using:
docker exec nextcloud-db mariadb \
-u nextcloud \
-pPASSWORD \
nextcloud > nextcloud_backup.sql
The individual service guides document the specific backup requirements for each application.
Part 14 — Troubleshooting Philosophy
Docker makes it very easy to create a complicated environment.
When something stops working, resist the temptation to immediately rebuild everything.
Work from the bottom upwards.
Is Docker running?
docker info
Is the container running?
docker ps
What does the container say?
docker logs <container>
Is it connected to the correct network?
docker inspect <container>
Can containers communicate?
For example:
docker exec <container> wget -qO- http://other-container:port/
Is Nginx Proxy Manager able to reach the upstream?
If the application works internally but returns a 502 externally, the problem is probably somewhere between NPM and the upstream container rather than with the application itself.
This layered approach makes troubleshooting much easier.
The Result
The finished environment is effectively a small private cloud running on a single Mac Mini:
INTERNET
│
Cloudflare DNS
│
Home Router
:80 / :443
│
▼
┌──────────────────────┐
│ Nginx Proxy Manager │
│ HTTPS / Let's Encrypt│
└──────────┬───────────┘
│
proxy-net
│
┌───────────────────┼───────────────────┐
│ │ │
▼ ▼ ▼
Nextcloud Immich Ghost
+ Collabora + Kiosk Blog
│ │ │
├───────────────┬───┴───────────────────┤
│ │ │
▼ ▼ ▼
Vaultwarden Paperless Homebox
Passwords Documents Inventory
│ │ │
├───────────────┼───────────────────────┤
▼ ▼ ▼
Calibre Uptime Kuma Mail Relay
Book Library Monitoring Postfix → Brevo
The individual applications perform very different jobs, but they share the same underlying infrastructure:
Docker provides isolation.
Docker Compose provides repeatable configuration.
Persistent volumes preserve the data.
proxy-net provides internal connectivity.
Nginx Proxy Manager provides the secure external gateway.
Cloudflare provides DNS.
Let's Encrypt provides certificates.
Uptime Kuma provides monitoring.
Backups provide recovery.
Together they turn a small Mac Mini into a surprisingly capable self-hosted server.