Gravity-Sync Alternative –- Nebula-Sync: The Next Best in Pi-hole Replication
Gravity-Sync is archived, but your Pi-hole replication doesn't have to die with it. In this guide, I walk through replacing it with Nebula-Sync using Docker on a Raspberry Pi—complete with cron scheduling, VLAN tips, and real-world configs.
With the recent upgrade to Pi-hole 6.x and the archiving of Gravity-Sync, many of us who relied on it for seamless synchronization between multiple Pi-hole instances have been left searching for a modern, reliable replacement.
Enter Nebula-Sync—a fresh, actively maintained alternative that brings back the automation and peace of mind we came to love with Gravity-Sync. Nebula-Sync offers:
✅ Full synchronization using Pi-hole’s Teleporter feature
✅ Selective synchronization of features
✅ Ability to run on a cron schedule for regular updates
In this post, I’ll walk you through how I replaced Gravity-Sync with Nebula-Sync in my own home network. My goal was to keep my Pi-hole instances in perfect sync, ensuring consistent ad-blocking and DNS rules across the board—without any major overhaul.
For now, I'm starting out with the Docker station connected over Wi-Fi, which works fine for initial setup and testing. That said, I plan to switch over to a wired Ethernet connection soon—I just need to free up a port on my switch. A more stable network link is always ideal for persistent services like this.
This article assumes you already have two or more Pi-hole instances running. We’ll start by setting up a Raspberry Pi 3B+ as a lightweight Docker station. From there, I’ll guide you through installing Docker, setting up Nebula-Sync, and integrating it with your existing Pi-hole deployments.
Let’s dive in.
Setting Up a Raspberry Pi 3B+ as a Dedicated Docker Station
To keep my network organized and offload container workloads from my main devices, I decided to repurpose a Raspberry Pi 3B+ as a lightweight Docker station. This Pi will host Nebula-Sync and any other tools I want to run in containers.
Unless I need a specific Linux distro for a project, I prefer to keep things simple by using the official Raspberry Pi OS Lite (headless/server edition). It’s minimal, clean, and perfect for most projects.
📦 Requirements:
- Raspberry Pi (I'm using a Pi 3B+ for this project) with power supply
- microSD card (16GB minimum, 32GB recommended)
- microSD card reader
- Raspberry Pi Imager (mine is installed on macOS)
- Wi-Fi credentials or Ethernet (for first boot)
Optional: Remove Gravity-Sync (If Previously Installed)
If you're replacing an old Gravity-Sync setup, it's a good idea to fully uninstall it before moving forward. This avoids any conflicts or confusion.
Run the following command on any system where Gravity Sync is installed:
gravity-sync purge
This will remove Gravity Sync and clean up its configuration files.
💡 Pro tip: I recommend rebooting the server afterwards, then running thegravity-sync
command again to confirm it’s fully removed. If you see an error likecommand not found: gravity-sync
, you’re good to go.
Step 1: Flashing Raspberry Pi OS Lite on macOS
We'll start by preparing the SD card with Raspberry Pi OS Lite (64-bit), which is ideal for headless setups like this one.
🔧 Flash the OS:
- Insert your microSD card into your Mac.
- Open Raspberry Pi Imager.
- For Operating System, choose:
→Raspberry Pi OS Lite (64-bit)
- For Storage, select your SD card.
- Before clicking “Write”, click the ⚙️ settings icon in the bottom right corner to pre-configure:
- Set a hostname (e.g.,
docker-station.local
) - Enable SSH
- Set a username and password
- Configure Wi-Fi if you’re not using Ethernet
- Set a hostname (e.g.,
- Click Save, then hit Write.
Once the image is written and verified, eject the SD card and insert it into your Raspberry Pi.
Step 2: First Boot and SSH Access
Now that the OS is flashed, it’s time to boot the Raspberry Pi and connect to it remotely.
- Insert the SD card into your Raspberry Pi.
- Power it on and connect it to your network (via Ethernet or Wi-Fi, depending on your pre-config).
- From your Mac (or any system on the same network), open a terminal and SSH into the Pi:
ssh your-username@docker-station.local
Replaceyour-username
with the username you configured in Raspberry Pi Imager.
If the hostname doesn't resolve, use the Pi's IP address instead (you can find it via your router or a network scanner likearp -a
).
You should be prompted for your password. Once logged in, you're in!
Step 3: Update, Upgrade & Clean the System
Before installing Docker, it’s a good idea to bring the system fully up to date and clean out unnecessary packages.
Run the following commands:
sudo apt update && sudo apt upgrade -y
sudo apt autoremove -y
sudo apt clean
This ensures your system has the latest security patches and package versions, and it's free of unnecessary clutter.
Step 4: Installing Docker on the Raspberry Pi
With the Pi updated and ready to go, it's time to install Docker. Docker makes it easy to manage lightweight containers—perfect for tools like Nebula-Sync.
📥 Install Docker the Official Way
Docker provides a convenient installation script that works great on Raspberry Pi OS:
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh
This script:
- Adds Docker’s official APT repository
- Installs the latest Docker Engine and CLI
- Sets up the required system services
When it’s finished, confirm Docker is installed:
docker --version
You should see output like:
Docker version 28.0.4, build b8034c0
👤 Add Your User to the Docker Group
To run Docker commands without sudo
, add your user to the docker
group:
sudo usermod -aG docker $USER
After that, log out and back in (or reboot the Pi) for the changes to take effect:
sudo reboot
Once you're back in, test it out:
docker run hello-world
⬇️ Output:
$ docker run hello-world
Unable to find image 'hello-world:latest' locally
latest: Pulling from library/hello-world
c9c5fd25a1bd: Pull complete
Digest: sha256:272756e837f03739a728b026250e01e0917e00f01f76271
Status: Downloaded newer image for hello-world:latest
Hello from Docker!
This message shows that your installation appears to be working correctly.
To generate this message, Docker took the following steps:
1. The Docker client contacted the Docker daemon.
2. The Docker daemon pulled the "hello-world" image from the Docker Hub.
(arm64v8)
3. The Docker daemon created a new container from that image which runs the
executable that produces the output you are currently reading.
4. The Docker daemon streamed that output to the Docker client, which sent it
to your terminal.
If everything’s working, you’ll see Docker’s "Hello from Docker!" message. 🎉
—let’s get Nebula-Sync up and running in Docker!
Step 5: Deploying Nebula-Sync with Docker
Now that Docker is installed and running on your Raspberry Pi, it’s time to set up Nebula-Sync. This tool uses Pi-hole’s built-in Teleporter feature to keep two (or more) instances in sync with minimal fuss.
I first came across Nebula Sync through Techno Tim’s excellent walkthrough, which inspired this setup. His guide helped me understand the flow and gave me a jumpstart in building my own version with a dedicated Docker station.
This guide assumes you already have at least two Pi-hole instances up and running.
📁 Create a Directory for Nebula Sync
Let’s keep things tidy by setting up a dedicated folder:
mkdir -p ~/nebula-sync
cd ~/nebula-sync
🛠️ Create the Docker Compose File
Using nano
or your preferred editor, create a docker-compose.yml
file:
nano docker-compose.yml
Paste the following content:
---
services:
nebula-sync:
container_name: nebula-sync
image: ghcr.io/lovelaze/nebula-sync:latest
restart: unless-stopped
env_file:
- .env
environment:
- TZ=America/New_York # Change to your timezone
Note: We’re using theenv_file
directive to keep our configuration clean and separate. This makes it easier to update settings without modifying thedocker-compose.yml
file itself.
Save and exit (CTRL+O
, Enter
, then CTRL+X
).
You should now see the file listed:
$ ls
docker-compose.yml
✏️ Create the Environment File
Next, let’s define our actual Nebula-Sync configuration in a .env
file:
nano .env
Paste the following (and adjust to match your Pi-hole instances and preferences):
PRIMARY="https://192.168.10.5|password" # Be sure to add quotes if you have special characters
REPLICAS="https://192.168.10.20|password,https://172.16.10.10|password" # Be sure to add quotes if you have special characters
FULL_SYNC=false
RUN_GRAVITY=false
CRON=*/15 * * * *
CLIENT_SKIP_TLS_VERIFICATION=true
TZ=America/New_York
SYNC_CONFIG_DNS=true
SYNC_CONFIG_DHCP=false
SYNC_CONFIG_NTP=false
SYNC_CONFIG_RESOLVER=false
SYNC_CONFIG_DATABASE=false
SYNC_CONFIG_MISC=false
SYNC_CONFIG_DEBUG=false
SYNC_GRAVITY_DHCP_LEASES=false
SYNC_GRAVITY_GROUP=false
SYNC_GRAVITY_AD_LIST=true
SYNC_GRAVITY_AD_LIST_BY_GROUP=true
SYNC_GRAVITY_DOMAIN_LIST=true
SYNC_GRAVITY_DOMAIN_LIST_BY_GROUP=true
SYNC_GRAVITY_CLIENT=false
SYNC_GRAVITY_CLIENT_BY_GROUP=false
Save and exit (CTRL+O
, Enter
, then CTRL+X
).
🔍 A Quick Breakdown:
- PRIMARY: This is your main Pi-hole instance. Changes made here will be exported and sent to the replicas.
- REPLICAS: These are your secondary Pi-hole instances that will receive sync updates from the primary. Separate multiple replicas with a comma.
Example: I have one main Pi-hole and two replicas syncing from it.
🔄 Current Sync Strategy (My Setup)
I won’t go into every available setting, but here’s what I’m using right now (this is subject to change as I tweak things):
- ✅ Syncing my DNS configs, AdLists, and domain lists
- 🔁 Running on a cron schedule every 15 minutes
- ⚠️ DHCP, NTP, and Resolver syncing are disabled in my setupI
- ❌ Not running
gravity
updates from Nebula Sync (i.e.,RUN_GRAVITY=false
)
⚠️ From what I understand, the default behavior for updating Gravity is once a week. You can change this behavior if needed by enablingRUN_GRAVITY
and setting your preferred schedule withCRON
.
Feel free to tailor this setup to your environment and adjust the sync behavior as needed.
🚀 Step 6: Start Nebula Sync and Test Your First Sync
With your config files in place, it’s time to launch Nebula Sync and confirm that everything’s working.
▶️ Start the Compose Stack
Start your stack interactively to monitor logs as it starts:
docker compose up
Should see:
$ docker compose up
[+] Running 1/1
✔ Container nebula-sync Created 0.0s
Attaching to nebula-sync
nebula-sync | 2025-03-30T12:09:23-04:00 INF Starting nebula-sync v0.8.0
nebula-sync | 2025-03-30T12:09:23-04:00 INF Running sync mode=selective replicas=1
nebula-sync | 2025-03-30T12:09:23-04:00 INF Authenticating clients...
nebula-sync | 2025-03-30T12:09:25-04:00 INF Syncing teleporters...
nebula-sync | 2025-03-30T12:09:26-04:00 INF Syncing configs...
nebula-sync | 2025-03-30T12:09:26-04:00 INF Invalidating sessions...
nebula-sync | 2025-03-30T12:09:26-04:00 INF Sync complete
Later, after we are done testing, we will start it in the background (daemon mode):
docker compose up -d
🔍 Check the Container Status
To confirm the container is running:
docker ps | grep nebula
You should see a line showing nebula-sync
is up and healthy.
📄 View Container Logs
To check recent logs and verify everything is running smoothly:
$ docker logs nebula-sync
2025-03-30T12:07:24-04:00 INF Sync complete
2025-03-30T12:09:23-04:00 INF Starting nebula-sync v0.8.0
2025-03-30T12:09:23-04:00 INF Running sync mode=selective replicas=1
2025-03-30T12:09:23-04:00 INF Authenticating clients...
2025-03-30T12:09:25-04:00 INF Syncing teleporters...
2025-03-30T12:09:26-04:00 INF Syncing configs...
2025-03-30T12:09:26-04:00 INF Invalidating sessions...
2025-03-30T12:09:26-04:00 INF Sync complete
🧪 Run a Manual Sync
Test your setup with a manual sync:
docker exec nebula-sync sync
If everything is configured properly, you’ll see output similar to:
2025-03-30T12:34:27-04:00 INF Starting nebula-sync v0.8.0
2025-03-30T12:34:27-04:00 INF Running sync mode=selective replicas=1
2025-03-30T12:34:27-04:00 INF Authenticating clients...
2025-03-30T12:34:29-04:00 INF Syncing teleporters...
2025-03-30T12:34:29-04:00 INF Syncing configs...
2025-03-30T12:34:29-04:00 INF Invalidating sessions...
2025-03-30T12:34:29-04:00 INF Sync complete
Success! 🎉 Your Pi-hole configurations are now synced from the primary to all replicas.
🔁 Check the Cron Job (Optional)
If you've set a cron job like:
CRON=*/15 * * * *
Nebula-Sync will automatically sync every 15 minutes. You can confirm it’s working by:
docker logs nebula-sync
💡 Note: The default behavior for Gravity updates (ifRUN_GRAVITY=false
) is once per week. You can override this by enablingRUN_GRAVITY=true
and adjusting your cron schedule accordingly.
🚀 Final Deployment in Daemon Mode
Once everything is confirmed working and syncing properly, run Nebula-Sync in background (daemon) mode:
docker compose up -d
This will keep it running and syncing according to the schedule you’ve defined in .env
.
🧠 Tip: Manage Everything from the Primary
Once Nebula-Sync is in place, only add, remove, or edit settings/lists on the PRIMARY Pi-hole. Changes should always flow from the primary to the replicas.
If you edit anything directly on a replica, it will likely be overwritten or cause inconsistencies during the next sync cycle.
🧠 Networking Tip: VLAN Awareness
If your Pi-hole instances live on a separate VLAN from your main LAN, then you probably already know which network your Nebula-Sync container needs to live on. I won't go deep into VLAN tagging or firewall rules here—everyone's setup is different, and that’s a rabbit hole of its own.
🔧 Just make sure that Nebula Sync has proper network access to all Pi-hole instances—especially ports 80/443 for the web interface and API.
If all else fails or you're unsure, you can always deploy the Nebula-Sync container in the same VLAN as your Pi-holes to keep things simple and avoid connectivity issues.
📚 Resources & Shoutouts
- Nebula Sync GitHub Repository – Official source and documentation
- Techno Tim’s Blog Post – The guide that inspired me to dive into Nebula Sync in the first place. Highly recommended if you want a second perspective.
- Pi-hole Documentation – For configuring Pi-hole, Unbound, DNS settings, and AdLists
✍️ Final Thoughts
Nebula-Sync has proven to be a reliable and modern replacement for Gravity-Sync in my home network. With everything containerized, scheduled, and automatically replicated, managing multiple Pi-hole instances is now effortless.
If you followed along and got value out of this guide, I’d love to hear from you. Whether you’re running a full-blown homelab or just getting started, Bryan’s Backbone is all about sharing the tools and ideas that help us build strong, resilient systems.
Until next time—
Keep your backbone strong. Keep your packets clean.— Bryan @ Bryan’s Backbone
🔗 Visit my portfolio at brysteele.com