How I Installed Pi-hole on macOS with OrbStack

A practical, local-only guide to running the official Pi-hole container on an Apple-silicon Mac, routing Ethernet DNS through it, testing blocking, handling passwords, updates and emergency rollback.

Illustration of a MacBook running Pi-hole in a local OrbStack container, with DNS requests filtering advertising and tracking domains.

I wanted network-level advertising and tracker filtering on my Apple-silicon MacBook Pro without modifying macOS itself or maintaining another physical device. Pi-hole is designed for supported Linux operating systems, while macOS is not listed as a native target, so the clean solution was to run the official Pi-hole image in a lightweight Docker environment.

This guide documents the exact local-only setup I used: OrbStack provides Docker on macOS, Pi-hole listens only on 127.0.0.1, and macOS sends DNS queries from the active Ethernet service to that local resolver. The result filters this Mac without exposing an open DNS service to the local network.

Scope: this configuration protects one Mac. It does not automatically filter every device on the LAN. That would require a different network-wide design, normally involving a continuously available Pi-hole host and router or DHCP changes.

Why I used OrbStack and Docker

Pi-hole's official prerequisites list maintained Linux distributions, not macOS. Pi-hole does, however, publish an official Docker image and a Docker Compose example. Apple silicon is also a good match for the ARM64 architecture supported by the Pi-hole resolver.

OrbStack is a lightweight Docker environment for macOS. It includes the Docker command-line tools and Docker Compose, while avoiding a manual Linux virtual-machine installation. The important architectural choice is not OrbStack itself—Docker Desktop could also run the container—but the container keeps Pi-hole isolated and reproducible.

What this setup looks like

macOS applications ↓ 127.0.0.1:53 ↓ Pi-hole container ↓ configured upstream DNS resolvers

The Pi-hole DNS and administration ports are bound to the loopback address. Other computers on the network cannot query this instance, and the web dashboard is available only from the Mac itself.

1. Install and start OrbStack

With Homebrew already installed:

brew install orbstack open -a OrbStack

After OrbStack starts, verify Docker and Compose:

docker version docker compose version

2. Confirm that DNS port 53 is available

Pi-hole must bind TCP and UDP port 53. Check for another local DNS service before starting the container:

sudo lsof -nP \ -iTCP:53 -sTCP:LISTEN \ -iUDP:53

No output normally means the port is free. If another process owns port 53, identify and resolve that conflict rather than terminating an unfamiliar system service blindly.

3. Create a persistent Pi-hole service directory

mkdir -p ~/Services/pihole cd ~/Services/pihole umask 077 printf 'PIHOLE_PASSWORD=%s\n' "$(openssl rand -hex 18)" > .env chmod 600 .env

The .env file becomes the source of the Pi-hole administration password used by Docker Compose. Keeping it permission-restricted prevents other local users from reading it.

4. Create the Compose configuration

Create ~/Services/pihole/compose.yaml with the following content:

services: pihole: container_name: pihole hostname: pihole image: pihole/pihole:latest ports: - "127.0.0.1:53:53/tcp" - "127.0.0.1:53:53/udp" - "127.0.0.1:8080:80/tcp" environment: TZ: "Australia/Adelaide" FTLCONF_webserver_api_password: "${PIHOLE_PASSWORD}" FTLCONF_dns_listeningMode: "ALL" FTLCONF_dns_upstreams: "9.9.9.9;149.112.112.112" volumes: - "./etc-pihole:/etc/pihole" restart: unless-stopped

Why these settings matter

  • 127.0.0.1:53 exposes DNS only to the Mac itself.
  • 127.0.0.1:8080 keeps the administration interface local.
  • FTLCONF_dns_listeningMode: "ALL" is required for the normal Docker bridge arrangement, even though host-side access remains restricted by the loopback port bindings.
  • ./etc-pihole:/etc/pihole preserves settings and database data when the container is recreated.
  • restart: unless-stopped restarts Pi-hole when the Docker engine returns.
  • Explicit upstream resolvers prevent Pi-hole from trying to resolve through itself.

The example uses Quad9 addresses. They can be replaced with other trusted upstream resolvers. Do not add an external resolver directly to the macOS network service as a “backup,” because macOS could then bypass Pi-hole.

5. Start Pi-hole

cd ~/Services/pihole docker compose pull docker compose up -d docker compose ps

Inspect the initial logs if the service is not healthy:

docker compose logs --tail=100 pihole

6. Test Pi-hole before changing macOS DNS

dig @127.0.0.1 pi-hole.net

A successful response should contain an address and show:

SERVER: 127.0.0.1#53

This proves that the local container is resolving DNS before macOS depends on it.

7. Open the administration interface

Open:

http://127.0.0.1:8080/admin/

To display the generated password:

grep '^PIHOLE_PASSWORD=' ~/Services/pihole/.env | cut -d= -f2-

8. Identify the active Ethernet service

macOS DNS settings are attached to named network services rather than simply to an interface such as en15. My Mac had several historical Ethernet and dock services, so I first listed the services that currently had addresses:

networksetup -listallnetworkservices | tail -n +2 | while IFS= read -r service; do ip=$(networksetup -getinfo "$service" 2>/dev/null | awk -F': ' '/^IP address:/{print $2}') if [[ -n "$ip" && "$ip" != "none" && "$ip" != "0.0.0.0" ]]; then printf "%-35s %s\n" "$service" "$ip" fi done

Then I checked the default route:

route -n get default | grep -E 'gateway:|interface:'

In my case the active interface was en15. I mapped that interface back to its macOS service name:

networksetup -listnetworkserviceorder | grep -B1 'Device: en15'

The result identified Thunderbolt Ethernet Slot 0. Use the service name shown on your own Mac; do not copy mine without checking.

9. Point the active Ethernet service to Pi-hole

sudo networksetup -setdnsservers "Thunderbolt Ethernet Slot 0" 127.0.0.1

Verify the configured DNS server:

networksetup -getdnsservers "Thunderbolt Ethernet Slot 0"

The output should be:

127.0.0.1

Flush the macOS DNS cache:

sudo dscacheutil -flushcache sudo killall -HUP mDNSResponder

10. Verify normal macOS resolution

This test deliberately does not specify a DNS server:

dig pi-hole.net

The response should again show:

SERVER: 127.0.0.1#53

At this point macOS is using Pi-hole through the selected Ethernet service.

11. Test actual blocking

I used a commonly blocked advertising domain:

dig doubleclick.net

My Pi-hole returned:

doubleclick.net. 2 IN A 0.0.0.0 SERVER: 127.0.0.1#53

I then tested the application connection:

curl -I --connect-timeout 5 https://doubleclick.net

The connection failed because the blocked hostname resolved to the local sink address. The same query appeared as blocked in red under Pi-hole's Query Log. Together, these tests confirmed DNS routing, blocklist matching and application-level failure.

12. Set your own administration password

If replacing the generated password, update the persistent .env value and recreate the container. The following avoids displaying the password while it is typed:

cd ~/Services/pihole read -s "NEWPASS?Enter your new Pi-hole password: " echo printf 'PIHOLE_PASSWORD=%s\n' "$NEWPASS" > .env chmod 600 .env unset NEWPASS docker compose up -d --force-recreate

A password containing a newline is not valid for this simple environment-file method. Be careful with quotation characters and complex shell-sensitive values; a long generated hexadecimal password is the least ambiguous option.

13. Start Pi-hole automatically

Add OrbStack under:

System Settings → General → Login Items & Extensions → Open at Login

Because the container uses restart: unless-stopped, Pi-hole should return when OrbStack's Docker engine starts. This is a login-time macOS service arrangement rather than a native pre-login Pi-hole daemon.

14. Know the rollback command before you need it

This design creates an intentional dependency: if Pi-hole or OrbStack is stopped, the Mac cannot resolve names while its DNS remains set to 127.0.0.1. Restore DHCP-provided DNS with:

sudo networksetup -setdnsservers "Thunderbolt Ethernet Slot 0" Empty sudo dscacheutil -flushcache sudo killall -HUP mDNSResponder

Again, substitute your actual network-service name.

15. Routine management

Status and logs

cd ~/Services/pihole docker compose ps docker compose logs --tail=100 pihole

Restart

docker compose restart

Stop and start

docker compose stop docker compose start

Update

Pi-hole's Docker documentation recommends replacing the container with a fresh image and reading release notes before upgrades:

cd ~/Services/pihole docker compose pull docker compose down docker compose up -d

The persistent volume keeps the Pi-hole data while the container is recreated. I would not enable unattended Pi-hole updates: DNS is critical infrastructure, and a failed update can remove name resolution from the Mac.

What Pi-hole can and cannot block

Pi-hole blocks at the DNS layer. It is effective against many advertising, tracking and telemetry hostnames used by websites and applications. It also gives useful visibility into which domains software is requesting.

It cannot distinguish an advertisement from desired content when both are served from the same hostname. This is why DNS filtering does not reliably remove every YouTube or first-party advertisement. A browser content blocker remains useful alongside Pi-hole.

Pi-hole also cannot calculate exact bandwidth savings. It knows that a DNS request was blocked, but because the connection never happens, it never sees how many bytes the advertisement or script would have transferred.

VPN and encrypted-DNS caveats

A VPN client may install its own DNS configuration and bypass the Ethernet service's resolver. Browsers or applications using their own DNS-over-HTTPS implementation can also avoid the system DNS path. When filtering appears inconsistent, inspect the effective DNS configuration and check whether queries appear in Pi-hole's Query Log.

Final result

This setup gave me a clean, reversible Pi-hole installation on an M1 MacBook Pro without altering macOS or exposing DNS to the surrounding network. The important parts were straightforward: use the official container, bind every published port to loopback, identify the real active macOS network service, verify resolution before and after changing DNS, and keep the rollback command close at hand.

The same pattern should work on later Apple-silicon Macs, provided Docker can bind local port 53 and the correct network service is configured.