DIY VPN with Docker

I’ve worked with both ExpressVPN and NordVPN. Both are great services but, from my perspective, have one major shortcoming: they’re currently blocked by Amazon Web Services (AWS). When using either of them you are simply not able to access any of the AWS services.

The most common scenario in which I’d be using a VPN is if I’m on a restrictive network where I’m only able to access web sites. Typically just ports 80, 8080 and 443 are open. Forget about SSH (port 22), SMTP (ports 25, 465 and 587) or NTP (port 123). I want to be able to connect by SSH to my AWS servers, send mail over SMTP and synchronise my clock. The latter items are normally possible over commercial VPN providers (like ExpressVPN and NordVPN) but not being able to connect to AWS is a deal breaker.

Luckily there is a simple solution: run your own VPN server. Using a low end cloud instance on AWS or DigitalOcean (costing $5 or less per month) this is eminently plausible.

Launch a Cloud Server

Obviously this step needs to be done before you actually need the VPN! Spin up a minimal Ubuntu server on the cloud service of your choice (we’ll be using AWS for illustration).

Open ports 1194 (UDP) and 443 (TCP) on the server.

Make a SSH connection to the remote server (assuming that port 22 is open by default!). The instructions which follow should all be executed on the remote server.


We’re going to need Docker, so install it now! We’ll be using the kylemanna/openvpn Docker image (source repository is here). Start by pulling the image.

docker pull $OVPN_IMAGE

The VPN configuration and certificates will be stored in a Docker volume. Create that now.

docker volume create --name $OVPN_DATA

You can check the contents of this volume using the following:

sudo ls /var/lib/docker/volumes/ovpn-data/_data

Grab the (public) DNS name for the server and stash it in a shell variable.


To reduce the volume of logging information it can be handy to include the --log-driver=none option with the following invocations of docker.


First we’ll set up a VPN operating over UDP on port 1194. From a bandwidth perspective this is efficient, but this port may well be closed (in which case see the TCP option below).

Generate the OpenVPN configuration.

docker run -v $OVPN_DATA:/etc/openvpn --rm $OVPN_IMAGE ovpn_genconfig -u udp://$DNSNAME -b

Initialise the EasyRSA Public Key Infrastructure (PKI).

docker run -v $OVPN_DATA:/etc/openvpn --rm -it $OVPN_IMAGE ovpn_initpki

Enter and verify a suitable private key (PEM) pass phrase when prompted. 🚨 Don’t leave this empty!

At the prompt for a Common Name, just accept the default. Boil the kettle. Enter the pass phrase when prompted. And again.

Launch the OpenVPN daemon process.

docker run --rm --name openvpn -v $OVPN_DATA:/etc/openvpn -d -p 1194:1194/udp --cap-add=NET_ADMIN $OVPN_IMAGE


Execute the commands below for a VPN over TCP on port 443 (this is the port for HTTPS, so is almost definitely going to be open, no matter how repressive the network!).

# This runs quickly and requires no input.
docker run -v $OVPN_DATA:/etc/openvpn --rm $OVPN_IMAGE ovpn_genconfig -u tcp://$DNSNAME:443 -b
# This takes a little longer and requires you to enter a passphrase.
docker run -v $OVPN_DATA:/etc/openvpn --rm -it $OVPN_IMAGE ovpn_initpki

Launch the daemon.

docker run \
  -d \
  --restart always \
  --name openvpn \
  -v $OVPN_DATA:/etc/openvpn \
  -p 443:1194/tcp \
  --cap-add=NET_ADMIN \

User and Configuration

Regardless of whether you are creating a VPN over TCP or UCP, you now need to create the configuration file which will be used with the openvpn client on your local machine.

Let’s set up a key for Alice.

docker run -v $OVPN_DATA:/etc/openvpn --rm -it $OVPN_IMAGE easyrsa build-client-full alice nopass

Enter the pass phrase when prompted. Now dump the configuration file.

docker run -v $OVPN_DATA:/etc/openvpn --rm $OVPN_IMAGE ovpn_getclient alice >alice.ovpn

How about a key for another user, Bob?

docker run -v $OVPN_DATA:/etc/openvpn --rm -it $OVPN_IMAGE easyrsa build-client-full bob
docker run -v $OVPN_DATA:/etc/openvpn --rm $OVPN_IMAGE ovpn_getclient bob >bob.ovpn

We didn’t specify the nopass option for Bob, so he’ll need to provide a password every time that he connects. This is probably a good idea!

Now disconnect from the server.


Now, back on your local machine use SFTP or SCP to get a local copy of the .ovpn file from the server.

Install OpenVPN.

sudo apt install openvpn

Connect to the VPN.

sudo openvpn --config alice.ovpn

If everything goes well then you should see “Initialization Sequence Completed”. Confirm that your effective IP address is now that of the VPN server. Enjoy!


This setup is simple and cost effective. Typically I’ll only need a VPN for a few days in succession, so it’s very convenient that I can literally spin up a VPN when I know that I’m going to need it, then take it down when I’m done. No long term commitment. No hassles accessing any port or protocol I need.