How to run OpenVPN and Pi-hole using Docker in a VPS

  • Wed Sep 04 18:51
  • No Comments
  • 383

This is a tutorial on how to run OpenVPN and Pi-hole in Docker with zero experience. Since Docker is a beast with its documentations and there are countless tutorials out there, I will be skipping a lot of things. It is your responsibility to learn afterwards.

Table of Contents


  • VPS – For this example, I will be using DigitalOcean. You can use any server you want as long as it can run Docker. I am currently hosted with BuyVM.
  • Domain – If don’t have one, you can register one for free at Freenom.

Setting Up – DigitalOcean

Go to the Create Droplets page and click on Marketplace tab. We’re gonna use a custom image to speed things up.

By using the Docker image, our VPS comes pre-installed with Docker and docker-compose. You can leave the other settings as default or modify location, that’s up to you.

Copy the IP address so we can paste it into the DNS of our new domain.

Setting Up – Freenom

Register your free domain, for this article, I registered a temporary domain for 1 month.

After you register your domain, go to your dashboard and click on the Manage Freedom DNS so we can enter the IP address of our VPS.

  • A – Leave the Name blank and paste your VPS IP as the target, this will be our main URL for Pi-hole’s dashboard
  • TRAEFIK CNAME – Subdomain for Traefik dashboard
  • VPN CNAME – Required for OpenVPN config

Setting Up – OpenVPN

docker run --rm \
--log-driver=none \
-v ovpn-data-demyx:/etc/openvpn \
kylemanna/openvpn ovpn_genconfig -u udp://

docker run --rm -it \
--log-driver=none \
-v ovpn-data-demyx:/etc/openvpn \
kylemanna/openvpn ovpn_initpki

First command will generate the OpenVPN files in the named volume “ovpn-data-demyx” along with our subdomain URL for the UDP.

Second command will generate our keys. It will prompt you for a passphrase. You can enter your domain name as the Common Name in the second prompt.

docker run -it --rm \
-v ovpn-data-demyx:/etc/openvpn \
kylemanna/openvpn vi /etc/openvpn/openvpn.conf

# Config should look like this
push "block-outside-dns"
push "dhcp-option DNS"
push "comp-lzo no"

Execute the Docker command to edit openvpn.conf and point it to our Pi-hole’s IPv4 address: Your config should look like the lines where it says “push.”

  • Once the terminal editor is opened, press the letter i to edit the text
  • Delete 1 of the DNS options and insert our custom address
  • To save: press ESC key, shift + colon, type wq, then press Enter key

Setting Up – Authentications

If you want to generate a strong password and basic auth, feel free to use my image.

docker run -it --rm demyx/utilities "pwgen -cns 50 1"
docker run -it --rm demyx/utilities "htpasswd -nb username password"

First command generates a 50 character password and the second one generates your basic auth, used for Traefik and Pi-hole dashboard security logins. Modify the parameters to your needs.


YAML (“YAML Ain’t Markup Language”) is a human-readable data-serialization language. It is commonly used for configuration files, but could be used in many applications where data is being stored (e.g. debugging output) or transmitted (e.g. document headers).

version: "3.7"
    image: traefik
    container_name: demyx_traefik
    restart: unless-stopped
      - demyx
      - 80:80
      - 443:443
      - /var/run/docker.sock:/var/run/docker.sock:ro
      - demyx_traefik:/demyx
      - TRAEFIK_API=true
      - [email protected]
      - TRAEFIK_LOG=true
      - TRAEFIK_LOG_FILEPATH=/demyx/error.log
      - TRAEFIK_ACCESSLOG_FILEPATH=/demyx/access.log
      - TZ=America/Los_Angeles
      - "traefik.enable=true"
      - "traefik.http.routers.traefik-http.rule=Host(``)"
      - "[email protected]"
      - "traefik.http.routers.traefik-http.entrypoints=http"
      - "traefik.http.routers.traefik-http.middlewares=traefik-redirect"
      - "traefik.http.routers.traefik-https.rule=Host(``)"
      - "traefik.http.routers.traefik-https.entrypoints=https"
      - "traefik.http.rou[email protected]"
      - "traefik.http.routers.traefik-https.tls.certresolver=demyx"
      - "traefik.http.routers.traefik-https.middlewares=traefik-auth"
      - "traefik.http.middlewares.traefik-auth.basicauth.users=demyx:$$apr1$$EqJj89Yw$$WLsBIjCILtBGjHppQ76YT1" # Basic auth password is demyx
      - "traefik.http.middlewares.traefik-redirect.redirectscheme.scheme=https" 
    image: kylemanna/openvpn
    container_name: demyx_openvpn
    restart: unless-stopped
      - ovpn-data-demyx:/etc/openvpn
      - NET_ADMIN
      - 11941:1194/udp
      - pihole
    image: kylemanna/openvpn
    container_name: demyx_openvpn_iphone
    restart: unless-stopped
      - ovpn-data-demyx:/etc/openvpn
      - NET_ADMIN
      - 11942:1194/udp
      - pihole
    container_name: demyx_pihole
    image: pihole/pihole
    restart: unless-stopped
      - pihole:/etc/pihole
      - pihole-dnsmasq:/etc/dnsmasq.d
      VIRTUAL_PORT: 80
      TZ: America/Los_Angeles # CHANGE THIS IF YOU WANT
      DNS1: ""
      DNS2: ""
      - "traefik.enable=true"
      - "traefik.http.routers.vpn-http.rule=Host(``)"
      - "traefik.http.routers.vpn-http.entrypoints=http"
      - "traefik.http.routers.vpn-http.middlewares=vpn-redirect"
      - "traefik.http.routers.vpn-https.rule=Host(``)"
      - "traefik.http.routers.vpn-https.entrypoints=https"
      - "traefik.http.routers.vpn-https.tls.certresolver=demyx"
      - "traefik.http.middlewares.vpn-redirect.redirectscheme.scheme=https"
      - "traefik.http.routers.vpn-https.service=vpn"
      - ""
      - "traefik.http.routers.vpn-https.middlewares=vpn-auth"
      - "traefik.http.middlewares.vpn-auth.basicauth.users=demyx:$$apr1$$EqJj89Yw$$WLsBIjCILtBGjHppQ76YT1" # Basic auth password is demyx
    name: ovpn-data-demyx
    name: pihole
    name: pihole-dnsmasq
    name: traefik
    name: demyx
    name: pihole
      driver: default
        - subnet:

Modify the necessary info first (email/domain/passwords), copy all of this, save it as docker-compose.yml, and then run:

docker-compose up -d
docker run -it --rm \
--name demyx_ctop \
-v /var/run/docker.sock:/var/run/docker.sock:ro \

If everything went well, you should see all of the containers are up and running by executing the command above. You should also check if both of the dashboards are up. Both are protected by basic auth credentials:

  • Basic Auth Username: demyx
  • Basic Auth Password: demyx
  • Pi-hole Default Password: demyx

Once you confirmed the services and URLs are up and running, we can now configure Pi-hole.


Log-in using the password you put in WEBPASSWORD in the YAML, then go to the DNS tab in the Settings page. Be sure to hit Save at the bottom right of the page.

On the same page, click the Blocklists tab, add this url then click Save and Update.

  [i] Pi-hole blocking is enabled
  [i] Neutrino emissions detected...
  [✓] Pulling blocklist source list into range

  [i] Target: (hosts)
  [✓] Status: Retrieval successful

  [i] Target: (justdomains)
  [✓] Status: No changes detected

  [i] Target: (hosts)
  [✓] Status: No changes detected

  [i] Target: (blocklist.php?download=domainblocklist)
  [✓] Status: Retrieval successful

  [i] Target: (simple_tracking.txt)
  [✓] Status: No changes detected

  [i] Target: (simple_ad.txt)
  [✓] Status: No changes detected

  [i] Target: (ad_servers.txt)
  [✓] Status: No changes detected

  [i] Target: (
  [✓] Status: Retrieval successful

  [✓] Consolidating blocklists
  [✓] Extracting domains from blocklists
  [i] Number of domains being pulled in by gravity: 1641132
  [✓] Removing duplicate domains
  [i] Number of unique domains trapped in the Event Horizon: 1535639
  [i] Nothing to whitelist!
  [i] Number of regex filters: 0
  [✓] Parsing domains into hosts format
  [✓] Cleaning up stray matter

  [✓] DNS service is running
  [✓] Pi-hole blocking is Enabled

Notice from the output: Number of unique domains trapped in the Event Horizon: 1535639.


Now go back to your dashboard and check the box on the far right.

One last thing is to add a cron job to auto update the block lists. I’m not sure Pi-hole container already does this but add it anyways:

docker exec demyx_pihole pihole updateGravity


Notice in my YAML that I have 2 services of OpenVPN. This is to show per device queries used for Pi-hole dashboard logs. Also notice that each of the services are using different ports.

OpenVPN defaults to port 1194 but you can only use a port once on the host OS. What I’ve done is add an extra number at the end and increment it. You have to do this per device if you’re planning to add more and you will see why.

# Creating OpenVPN profiles

docker run --rm -it \
-v ovpn-data-demyx:/etc/openvpn \
--log-driver=none \
kylemanna/openvpn easyrsa build-client-full macbook nopass

docker run --rm -it \
-v ovpn-data-demyx:/etc/openvpn \
--log-driver=none \
kylemanna/openvpn easyrsa build-client-full iphone nopass

We’re gonna create our OpenVPN profiles per devices. As you can see, the commands are executed separately for each devices.

# Exporting OpenVPN profiles

docker run --rm \
-v ovpn-data-demyx:/etc/openvpn \
--log-driver=none \
kylemanna/openvpn ovpn_getclient macbook > macbook.ovpn

docker run --rm \
-v ovpn-data-demyx:/etc/openvpn \
--log-driver=none \
kylemanna/openvpn ovpn_getclient iphone > iphone.ovpn

Our profiles are exported but we’re not done yet. We need to change the default port (1194) that’s in the profiles by executing the commands below:

# Search and replace

sed -i 's/1194/11941/g' macbook.ovpn
sed -i 's/1194/11942/g' iphone.ovpn

Now you can export these profiles to your devices.

Connecting – Computer

OpenVPN has clients for all major operating systems but for some reason, the MacOS client is hidden deep in their website, link here. Linux does not need the client because all the major Desktop Environments (DE) have it built-in.

# Print out the macbook.ovpn profile

cat macbook.ovpn

Copy the output, create a file named macbook.ovpn, and save it. After you install the macOS OpenVPN client, import it.

Connecting – Phone

Repeat the steps from the macOS profile and either save it to your iCloud Drive, iMessage, or email it to yourself. I chose iCloud Drive. You will need to download the OpenVPN app from the App Store.

Connecting – Linux

I’m running Manjaro KDE so your settings will probably be different. Open your network manager and find a VPN panel.

Yes. You guessed correctly. I am running linux on an iMac.


As you can see, our devices are connected in the Network page. If you find any errors or want to improve upon this, then please leave a comment below. If you find this article useful then please share it!

Buy me a coffeeBuy me a coffee