https://github.com/gautada/vpn
A basic VPN container
https://github.com/gautada/vpn
container network
Last synced: 2 months ago
JSON representation
A basic VPN container
- Host: GitHub
- URL: https://github.com/gautada/vpn
- Owner: gautada
- Created: 2020-12-24T14:55:25.000Z (over 5 years ago)
- Default Branch: main
- Last Pushed: 2026-02-26T19:32:38.000Z (4 months ago)
- Last Synced: 2026-02-28T03:55:44.973Z (4 months ago)
- Topics: container, network
- Language: Shell
- Homepage:
- Size: 6.84 KB
- Stars: 0
- Watchers: 1
- Forks: 0
- Open Issues: 1
-
Metadata Files:
- Readme: README.md
Awesome Lists containing this project
README
# vpn
A containerised L2TP/IPsec VPN server that runs on the `gautada/debian` base image inside a Kubernetes cluster. Exposes a native system-VPN endpoint over the internet — no third-party app required. Connects macOS and iOS devices to your home network using the built-in VPN client on each platform.
[](https://github.com/gautada/vpn/actions/workflows/build.yml)
[](https://github.com/gautada/vpn/pkgs/container/vpn)
[](LICENSE)
---
## About
`gautada/vpn` runs an L2TP/IPsec server using [Libreswan](https://libreswan.org/) (IKE daemon) and [xl2tpd](https://github.com/xelerance/xl2tpd) (L2TP daemon), protected by [Fail2Ban](https://www.fail2ban.org/).
Key properties:
- **Protocol:** L2TP over IPsec (PSK) — natively supported by macOS and iOS with no extra software
- **Base image:** [`gautada/debian`](https://github.com/gautada/debian) — the shared Debian 13 foundation for the entire container fleet
- **Runtime:** Kubernetes — designed as a `Deployment` with a `LoadBalancer` service for internet exposure
- **Role:** Home network gateway — k8s pods can already reach the home network; this container extends that access to external macOS and iOS clients
---
## Prerequisites
- Kubernetes cluster (1.27+) with a `LoadBalancer` provider (or a `NodePort` on a publicly routable node)
- A public IP address assigned to the LoadBalancer service
- Required environment variables set as a Kubernetes `Secret` (see [Configuration](#configuration))
- Container image from `ghcr.io/gautada/vpn`
**`gautada/*` dependencies:**
| Image | Purpose |
|---|---|
| [`gautada/debian`](https://github.com/gautada/debian) | Base OS image |
---
## Deployment
### 1. Create the credentials Secret
```bash
kubectl create secret generic vpn-credentials \
--from-literal=VPN_IPSEC_PSK='' \
--from-literal=VPN_USER='' \
--from-literal=VPN_PASSWORD=''
```
### 2. Apply the Deployment and Service
```yaml
# vpn.yaml
apiVersion: apps/v1
kind: Deployment
metadata:
name: vpn
spec:
replicas: 1
selector:
matchLabels:
app: vpn
template:
metadata:
labels:
app: vpn
spec:
containers:
- name: vpn
image: ghcr.io/gautada/vpn:latest
securityContext:
privileged: true # required for iptables and kernel modules
envFrom:
- secretRef:
name: vpn-credentials
env:
- name: VPN_PUBLIC_IP
value: "" # set explicitly; auto-detect is unreliable in k8s
ports:
- containerPort: 500
protocol: UDP
name: ike
- containerPort: 4500
protocol: UDP
name: ike-nat
- containerPort: 1701
protocol: UDP
name: l2tp
---
apiVersion: v1
kind: Service
metadata:
name: vpn
spec:
type: LoadBalancer
selector:
app: vpn
ports:
- name: ike
port: 500
targetPort: 500
protocol: UDP
- name: ike-nat
port: 4500
targetPort: 4500
protocol: UDP
- name: l2tp
port: 1701
targetPort: 1701
protocol: UDP
```
```bash
kubectl apply -f vpn.yaml
```
### 3. Confirm the service is running
```bash
kubectl get pods -l app=vpn
kubectl get svc vpn
```
The `EXTERNAL-IP` column of the service is your VPN server address.
---
## Configuration
| Variable | Required | Default | Description |
|---|---|---|---|
| `VPN_IPSEC_PSK` | ✅ | — | IPsec pre-shared key |
| `VPN_USER` | ✅ | — | VPN username |
| `VPN_PASSWORD` | ✅ | — | VPN password |
| `VPN_PUBLIC_IP` | Recommended | auto-detect | Server's public IP address. Set explicitly in k8s — auto-detect is unreliable. |
| `VPN_DNS_SRV1` | ❌ | `8.8.8.8` | Primary DNS server pushed to VPN clients |
| `VPN_DNS_SRV2` | ❌ | `8.8.4.4` | Secondary DNS server pushed to VPN clients |
| `VPN_L2TP_NET` | ❌ | `192.168.42.0/24` | L2TP internal network |
| `VPN_L2TP_LOCAL` | ❌ | `192.168.42.1` | L2TP local (server-side) IP |
| `VPN_L2TP_POOL` | ❌ | `192.168.42.10-192.168.42.250` | IP range assigned to L2TP clients |
| `VPN_XAUTH_NET` | ❌ | `192.168.43.0/24` | IKEv1 XAuth internal network |
| `VPN_XAUTH_POOL` | ❌ | `192.168.43.10-192.168.43.250` | IP range assigned to XAuth clients |
---
## Usage
### macOS
1. Open **System Settings → VPN → Add VPN Configuration → L2TP over IPSec**
2. Fill in the fields:
- **Server Address:** Your `EXTERNAL-IP` from `kubectl get svc vpn`
- **Account Name:** Value of `VPN_USER`
3. Click **Authentication Settings…**
- **Password:** Value of `VPN_PASSWORD`
- **Shared Secret:** Value of `VPN_IPSEC_PSK`
4. Tick **Send all traffic over VPN connection** if you want full tunnel
5. Click **Connect**
### iOS
1. Open **Settings → General → VPN & Device Management → VPN → Add VPN Configuration**
2. Set **Type** to **L2TP**
3. Fill in the fields:
- **Description:** Home VPN (or any label)
- **Server:** Your `EXTERNAL-IP` from `kubectl get svc vpn`
- **Account:** Value of `VPN_USER`
- **Password:** Value of `VPN_PASSWORD`
- **Secret:** Value of `VPN_IPSEC_PSK`
4. Tap **Done**, then toggle the VPN on
---
## Architecture
```
Internet
│
▼
LoadBalancer Service (UDP 500, 4500, 1701)
│
▼
vpn Pod (gautada/vpn)
├── Libreswan (ipsec) — IKE / IPsec keying
├── xl2tpd — L2TP tunnel daemon
└── Fail2Ban — brute-force protection
│
▼
k8s cluster network → home network
```
**Container fleet position:**
| Layer | Image |
|---|---|
| OS base | [`gautada/debian`](https://github.com/gautada/debian) |
| VPN (this repo) | [`gautada/vpn`](https://github.com/gautada/vpn) |
| Mesh overlay (separate) | [`gautada/tailscale`](https://github.com/gautada/tailscale) |
`gautada/tailscale` handles Tailscale-based mesh access (a separate concern). This container handles the internet-facing native-VPN endpoint.
---
## License
MIT