Revised Dockerfile Security Release - v1.2.0
v1.2.0 | Revised Dockerfile Security Release
List of changes since v.1.0.0
This release focuses on Dockerfile security hardening and operational stability. The transition from v1.0.0 to v1.2.0 introduces production-grade standards for container health and reproducibility.
🛡️ Security & Integrity
- Immutable Base Image: Switched from a generic
ubuntu:24.04tag to a specific SHA256 digest pinning to ensure builds are reproducible and protected against upstream tag mutations. - GPG Key Verification: Added a fingerprint check (
BC5480EF...) during the build process to verify the authenticity of the NordVPN repository key, preventing potential MITM attacks. - Modern Keyring Management: Migrated from the deprecated
apt-key//etc/apt/trusted.gpg.d/method to the more secure/usr/share/keyrings/workflow. - Layer Cleanup: Enhanced build hygiene by explicitly removing
/tmp/nordvpn.ascand/root/.gnupgwithin the sameRUNlayer.
⚙️ Stability & Reliability
- Version Pinning: The
nordvpnpackage is now pinned to version4.6.0, ensuring consistent behavior across different build environments. - Advanced Entrypoint Logic:
- Replaced the static
sleep 5with a readiness poll that checks for daemon responsiveness before proceeding. - Implemented a SIGTERM/SIGINT trap to allow for graceful shutdowns of the NordVPN service.
- Improved the "keep-alive" mechanism to be signal-aware using a
while/waitloop instead oftail -f /dev/null. - Healthcheck Implementation: Added a native Docker HEALTHCHECK that monitors the NordVPN daemon status every 30 seconds to ensure the container is operational.
- Replaced the static
📝 Metadata & Documentation
- OCI Compliance: Added comprehensive
LABELmetadata following Open Container Initiative standards, including:- Authorship and Licensing (MIT).
- Project URLs and descriptions.
- Explicitly defined Capability Requirements (
NET_ADMIN,NET_RAW) for easier deployment reference.
🧹 Dependencies
- Added
gnupgto support the new secure key-handling workflow. - Removed
apt-transport-https(redundant in modern Ubuntu/Debian versions for HTTPS repos).
Instructions - (v1.2.0)
This guide will walk you through the creation of all of the files, their contents, and directories needed in order to route a Docker application container through a Docker container for NordVPN. We are using audiobookshelf as the routed container example in this guide, but by changing a few things, you can adapt this guide for any application container.
🛠️ 1. Create the Dockerfile for the NordVPN Container
Create a directory (e.g. sudo mkdir ~/nordvpn-meshnet/), open it (e.g. cd ~/nordvpn-meshnet/) and save the following as Dockerfile inside it (e.g. sudo nano Dockerfile, keyboard shortcut Shift+Insert to paste with formatting, then Ctrl+X to save, followed by y to confirm saving, then Enter to confirm filename):
FROM ubuntu:24.04@sha256:c4a8d5503dfb2a3eb8ab5f807da5bc69a85730fb49b5cfca2330194ebcc41c7b
LABEL org.opencontainers.image.authors="COLVDV" \
org.opencontainers.image.title="NordVPN Docker Gateway" \
org.opencontainers.image.description="NordVPN Docker Gateway with Meshnet" \
org.opencontainers.image.version="1.2.0" \
org.opencontainers.image.url="https://github.com/colvdv/nordvpn-docker-gateway" \
org.opencontainers.image.licenses="MIT" \
capabilities.net_admin="required" \
capabilities.net_raw="required"
# Optimized build layer
RUN apt-get update && apt-get install -y --no-install-recommends \
wget \
gnupg \
ca-certificates \
iproute2 \
iptables \
&& mkdir -p -m 0700 /root/.gnupg \
&& wget -qO /tmp/nordvpn.asc https://repo.nordvpn.com/gpg/nordvpn_public.asc \
# Verify fingerprint to prevent MITM attacks
&& gpg --dry-run --quiet --import --import-options show-only /tmp/nordvpn.asc | grep -q "BC5480EFEC5C081CE5BCFBE26B219E535C964CA1" \
&& cat /tmp/nordvpn.asc | gpg --dearmor > /usr/share/keyrings/nordvpn-keyring.gpg \
&& echo "deb [arch=amd64 signed-by=/usr/share/keyrings/nordvpn-keyring.gpg] https://repo.nordvpn.com/deb/nordvpn/debian stable main" > /etc/apt/sources.list.d/nordvpn.list \
&& apt-get update \
# Pinned to specific NordVPN version (4.6.0, the latest as of this writing) for reproducibility. Check https://nordvpn.com/blog/nordvpn-linux-release-notes/ or remove the version tag to pull the latest Linux release version.
&& apt-get install -y --no-install-recommends nordvpn=4.6.0 \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/* /tmp/nordvpn.asc /root/.gnupg
# HEALTHCHECK: Ensures the daemon is responsive and NordVPN is in a valid state
HEALTHCHECK --interval=30s --timeout=10s --retries=3 \
CMD nordvpn status | grep -qE "Status: Disconnected|Status: Connected" || exit 1
# Entrypoint Logic
# Clears stale PID/socket files to prevent startup failure after unclean shutdowns
# Polls for daemon readiness before proceeding
# Graceful shutdown handler
ENTRYPOINT ["/bin/bash", "-c", \
"rm -rf /run/nordvpn && mkdir -p /run/nordvpn && \
/etc/init.d/nordvpn start && \
timeout 30 bash -c 'until nordvpn status &>/dev/null; do sleep 1; done' && \
trap '/etc/init.d/nordvpn stop; exit 0' SIGTERM SIGINT; \
while true; do sleep 10 & wait $!; done"]
Remember to update/remove the nordvpn version tag (=4.6.0) to pull the desired/latest linux release.
⚙️ 2. Setup & Build
Create a persistent directory to keep your NordVPN login and Meshnet settings safe across container restarts:
sudo mkdir ~/nordvpn-meshnet/data
Build the nordvpn-docker image (note: remember the dot at the end of the command line):
sudo docker build -t nordvpn-docker .
🚀 3. Deploy the NordVPN Gateway Container
Run the container with the necessary networking permissions.
(Note: For audiobookshelf we map port 13378 on the host to port 80 in the container. Because our app will share this network, it will be accessible via port 80, or specify your preferred port.):
sudo docker run -d \
--name nordvpn-meshnet \
--hostname abs-meshnet \
--restart unless-stopped \
--init \
--cap-add=NET_ADMIN \
--cap-add=NET_RAW \
--device /dev/net/tun:/dev/net/tun \
--sysctl net.ipv6.conf.all.disable_ipv6=0 \
-v ~/nordvpn-meshnet/data:/var/lib/nordvpn \
-p 13378:80 \
nordvpn-docker
[!TIP]
Pro Tip: After starting the NordVPN Docker Container, interact with NordVPN using the following command formatdocker exec -it nordvpn-meshnet nordvpn <COMMAND>(e.g.docker exec -it nordvpn-meshnet nordvpn login --token <YOUR_TOKEN>to login to your NordVPN account using a token).
🔗 4. Link your Application Container (audiobookshelf Example)
In your application’s (audiobookshelf) docker-compose.yml (e.g., ~/audiobookshelf/docker-compose.yml), the "magic" happens with network_mode.
services:
audiobookshelf:
container_name: audiobookshelf
image: ghcr.io/advplyr/audiobookshelf:latest
network_mode: "container:nordvpn-meshnet" # Attach to the NordVPN container
volumes:
# Media directories
- /mnt/media/Audio:/Audio
- /mnt/media/Documents:/Documents
- /mnt/media/Video:/Video
# Application data
- /mnt/media/_SYSTEM/~Audiobookshelf/backups:/Audiobookshelf Backups
- /mnt/media/_SYSTEM/~Audiobookshelf/config:/config
- /mnt/media/_SYSTEM/~Audiobookshelf/metadata:/metadata
environment:
- TZ=America/Denver
- ABS_BIND_ADDRESS=0.0.0.0
restart: unless-stopped
Change the volume directories specified in the docker-compose.yml above to fit your setup.
Make sure all host volume paths exist before creating the audiobookshelf container in the next step.
This docker-compose.yml is a slightly modified version of the one we are instructed to create when following the official audiobookshelf guide for Docker Compose; instead of specifying the ports here, we've bound the application's network identity to the NordVPN container (nordvpn-meshnet), and in step 3 we mapped port 13378 to port 80 (or the one you specified) in the NordVPN container already. Your port mappings may be different depending on the application you are working with; see your application's documentation for more information.
✨ 5. Deploy the Application Container
Run the container: sudo docker-compose up -d
Conclusion & Notes 🎉
The NordVPN Container (nordvpn-meshnet) should now access the audiobookshelf container successfully, hurray!
- 🚫 LAN Access to the audiobookshelf container doesn't work with this setup, but since Meshnet uses the shortest path it can find, it goes through LAN when available. (You can test this by running a
traceroute.) - 🌐 To access audiobookshelf over Meshnet, open the Meshnet device IP (http://x.x.x.x/) or Meshnet device name in your browser from a linked Meshnet device (http://device-name.nord/ or http://device-nickname/), no port specification needed since the Meshnet container is pointing to port 80 now (unless you specified a different port earlier in step 3).
- 🏠 To access audiobookshelf from the local machine it is still http://localhost:13378/.