Docker Installation on Various Operating Systems
Table of Contents
- Prerequisites
- Directory layout
- Download the setup files
- Configure
.env - First start
- First login
- First peer and first route
- Verifying the installation
- Troubleshooting
- Backup and restore
- Updates
- Migration from older setups
1. Prerequisites
Hardware
| Resource | Minimum | Recommended |
|---|---|---|
| CPU | 1 vCPU | 2 vCPU |
| RAM | 1 GB | 2 GB |
| Disk | 20 GB | 40 GB (more for long activity logs and Caddy access logs) |
Software
- OS: Any modern Linux distribution (Debian 11+, Ubuntu 22.04+, Fedora, Rocky, Alma, Alpine). Tested on Debian 13.
- Docker Engine: 24.0 or newer
- Docker Compose: v2 (part of Docker Engine since 23.0)
- WireGuard kernel module: present on most modern kernels. The container does not need an external install, but WireGuard capabilities (
NET_ADMIN) must be grantable to the container.
DNS
Before starting the container you need one DNS A-record (plus optionally AAAA for IPv6) pointing to the public IP of the host:
gate.example.com. IN A 198.51.100.42
GateControl uses this name for two purposes:
- Admin UI via
GC_BASE_URL— Caddy provisions a Let's Encrypt certificate for it automatically on first start. - WireGuard endpoint if you also set
GC_WG_HOST=gate.example.com. (GC_WG_HOSTmay be a bare public IP instead, but using the same hostname simplifies peer configs.)
Per-route domains (for reverse-proxy routes you create later) need separate A-records pointing to the same host.
Ports
| Port | Protocol | Purpose | Must be reachable from |
|---|---|---|---|
| 80 | TCP | HTTP → HTTPS redirect, ACME HTTP-01 challenge | Internet |
| 443 | TCP | HTTPS admin UI and all reverse-proxy routes | Internet |
| 443 | UDP | HTTP/3 (optional but recommended) | Internet |
| 51820 | UDP | WireGuard VPN endpoint | Internet |
| 53 | TCP/UDP on 127.0.0.1 and on the VPN subnet gateway IP (10.8.0.1 by default) |
Internal DNS for VPN peers | Container only (loopback and WG interface) |
If anything already binds 127.0.0.1:53 on the host (a common cause: NetworkManager-dnsmasq, libvirt-dnsmasq, bind9), the GateControl container will refuse to start. systemd-resolved binds 127.0.0.53 and does not conflict. The entrypoint checks this explicitly and exits with a clear message if it finds another listener.
Open the first four ports in your cloud firewall / iptables / ufw before you start the container.
2. Directory layout
Create a dedicated deploy directory — separate from any cloned source repository. The recommended path is /opt/gatecontrol/:
/opt/gatecontrol/
├── docker-compose.yml # image, ports, volume
├── .env # your config (passwords, domain, etc.)
├── update.sh # helper to pull latest and restart
└── data/ # created on first start — holds DB, certs, keys, WG config
Why separate from the source repository:
- Cleaner mental model: "code" and "config" never get mixed.
- Safe to clone/wipe/update the source repo without losing production state.
- One-line backup:
tar czf backup.tar.gz /opt/gatecontrolcaptures everything.
mkdir -p /opt/gatecontrol
cd /opt/gatecontrol
3. Download the setup files
There are three options. They all end with the same files in /opt/gatecontrol/.
Option A — Interactive setup (recommended)
Download and run setup.sh. It installs Docker if missing, walks you through .env values interactively, generates secure secrets, and starts the container:
cd /opt/gatecontrol
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/setup.sh
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/docker-compose.yml
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/.env.example
bash setup.sh
Skip straight to §6 First login — setup.sh does the rest.
Option B — Manual (full control)
cd /opt/gatecontrol
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/docker-compose.yml
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/.env.example
curl -fsSLO https://raw.githubusercontent.com/CallMeTechie/gatecontrol/master/update.sh
chmod +x update.sh
cp .env.example .env
Proceed to §4 Configure .env.
Option C — Air-gapped / offline
For hosts without internet access during install, download the image tarball from a release:
curl -fsSLO https://github.com/CallMeTechie/gatecontrol/releases/latest/download/gatecontrol-image.tar.gz
docker load < gatecontrol-image.tar.gz
rm gatecontrol-image.tar.gz
Continue with the docker-compose.yml and .env.example from Option B.
4. Configure .env
Edit /opt/gatecontrol/.env. Three values are required; everything else has sensible defaults.
Required
| Variable | What it is | Example |
|---|---|---|
GC_ADMIN_PASSWORD |
Initial password for the admin account. Read only on first start — later changes happen in the UI. |
R7!xK2#wPq9$Lm4v |
GC_WG_HOST |
Public IP or hostname your VPN clients dial. Must be reachable from the internet on UDP/51820. | gate.example.com or 198.51.100.42 |
GC_BASE_URL |
Full URL of the admin UI. Caddy uses the hostname to request a Let's Encrypt certificate. | https://gate.example.com |
Strongly recommended
| Variable | Why | Example |
|---|---|---|
GC_CADDY_EMAIL |
Lets Encrypt contacts you for expiry warnings and certificate issues. Without it, certs still work but you get no recovery channel. | admin@example.com |
Optional — left empty for auto-generation
| Variable | Default behavior if unset |
|---|---|
GC_SECRET |
A 48-byte session secret is generated on first start and persisted to /data/.session_secret (chmod 600). |
GC_ENCRYPTION_KEY |
A 32-byte AES-256 key is generated and persisted to /data/.encryption_key (chmod 600). Back this up. Restoring a DB without the matching key fails. |
See .env.example in the repository for the full reference including WireGuard tuning, rate limits, timeouts, client update repos, and licensing keys.
Edit the file
cd /opt/gatecontrol
nano .env # or vim, or any editor
Set the three required values. Save and exit.
5. First start
cd /opt/gatecontrol
docker compose up -d
docker compose logs -f
On the first run the entrypoint does several things. Expect to see, in roughly this order:
» Auto-detected egress interface: <name>— the outbound network interface used for VPN NAT. If you seeeth0falling back, it is correct on most cloud VMs; hosts with non-standard NIC names are detected automatically.» MASQUERADE rule active: 10.8.0.0/24 → <iface>— iptables NAT rule installed.» Generating WireGuard server keypair...— one-time on first start only. The private key is persisted to./data/wireguard/wg0.confwithchmod 600.» Session secret generated and saved— one-time on first start.» Encryption key generated and saved— one-time on first start.» Generating dnsmasq config (split-horizon for <hostname> → 10.8.0.1)...— internal DNS config.» Exporting Caddy JSON from DB...— on a fresh install the DB has no user-defined routes, but the management-UI route (GC_BASE_URLhostname →127.0.0.1:3000) is injected automatically. You do not need to configure this route by hand.» Starting services via supervisord...- Caddy boots, acquires Let's Encrypt certificate for the
GC_BASE_URLhostname. First cert fetch takes 10–30 seconds. Watch for:obtaining certificatefollowed bycertificate obtained successfully- If you see
lookup <hostname>: no such hostorunable to fetch certificate, DNS is not yet propagated — wait and the ACME client will retry.
- WireGuard starts, interface
wg0comes up. - Node.js web application starts on
127.0.0.1:3000. Caddy proxies requests on theGC_BASE_URLhostname to it.
Exit the log tail with Ctrl+C — the container keeps running in the background.