Introducción

Hay decenas de tutoriales sobre cómo levantar WireGuard con Docker. La mayoría cubren el “happy path”: copias el docker-compose.yml, corres docker compose up -d y listo. Este post no es solo ese tutorial.

Esto es lo que realmente pasó al montar mi propia VPN sobre una VM en Oracle Cloud, región Bogotá, incluyendo el error que me tocó resolver en el camino, y por qué lo hice sobre mi propia infraestructura en lugar de pagar un proveedor comercial.

¿Qué es una VPN y qué es WireGuard?

Una VPN (Virtual Private Network) crea un túnel cifrado entre tu dispositivo y un servidor remoto. Todo tu tráfico de internet sale por ese servidor, no por la red local en la que estás conectado. Esto sirve para dos cosas: cifrar tu tráfico en redes que no controlas (un café, un aeropuerto, el wifi de un hotel) y hacer que tu tráfico parezca originarse en la ubicación del servidor, no en la tuya real.

WireGuard es un protocolo de VPN relativamente nuevo frente a OpenVPN o IPSec. Su ventaja principal es la simplicidad: el código fuente del kernel module es una fracción del de OpenVPN, lo que reduce la superficie de ataque y facilita la auditoría. Usa criptografía moderna por defecto (no hay que elegir entre quince combinaciones de cifrados) y, en la práctica, es notablemente más rápido y con menor latencia que las alternativas tradicionales.

¿Por qué montar tu propia VPN en vez de usar un proveedor comercial?

Los proveedores comerciales de VPN (NordVPN, ExpressVPN, etc.) son más fáciles de configurar y ofrecen servidores en decenas de países. Pero tienen un problema de fondo: tienes que confiar en que no guardan logs de tu tráfico, algo que no puedes verificar por tu cuenta (algunos publican auditorías independientes, pero son revisiones puntuales).

Montar tu propia VPN sobre un VPS que ya pagas (o que está en el free tier, como mi caso en Oracle Cloud) tiene ventajas concretas:

  • Control total sobre los logs: no hay logs porque yo decido qué se registra.
  • Costo marginal cero: el VPS ya existe para otras cosas (mi blog, Nginx Proxy Manager).
  • IP propia, no compartida: las IPs de los proveedores comerciales las usan miles de personas a la vez y suelen estar identificadas como VPN; la de mi VM solo la uso yo.
  • Ubicación exacta: elijo la región, no “algún servidor en algún país” de una lista.

Este último punto es justo el motivo de este post.

Mi escenario y objetivo

Uso mi portátil fuera de casa con frecuencia, conectado a redes que no controlo. Quiero cifrar ese tráfico, sí, pero también necesito que mi salida a internet se vea como si estuviera en Colombia.

¿Por qué importa esto? Varios servicios colombianos —banca, comercio electrónico— bloquean o piden verificación extra cuando detectan que la conexión viene de un servidor VPN en otro país. Si mi VPN sale por un datacenter en Estados Unidos o Europa, termino peleando con mi propio banco.

La solución: una VM en la región de Oracle Cloud en Bogotá. El tráfico sale con una IP pública colombiana, así que para cualquier servicio local, estoy navegando desde Bogotá, aunque físicamente esté en otro lugar.

Arquitectura

[Mi portátil] --túnel WireGuard cifrado--> [VM Oracle Cloud, Bogotá] --> Internet
                                                  |
                                           IP pública colombiana

Todo el tráfico de mi portátil sale por la VM. Para cualquier sitio que visite, el origen es la IP pública de esa VM en Bogotá.

Prerrequisitos

  • Una VM en Oracle Cloud (en mi caso, instancia ARM/aarch64, free tier) con Docker y Docker Compose instalados.
  • En las reglas de red de la VM (Security List o Network Security Group), una regla de ingreso que permita UDP en el puerto 51820 desde 0.0.0.0/0 (o restringido a tus IPs conocidas, si prefieres no exponerlo a todo internet).
  • Si la VM tiene firewall local activo (firewalld, iptables), el mismo puerto debe estar abierto ahí también. En mi caso no tuve que tocar nada adicional a nivel de SO.

Despliegue con Docker Compose

Uso la imagen de linuxserver/wireguard, que simplifica bastante la generación de peers frente a configurar WireGuard a mano.

services:
  wireguard:
    image: lscr.io/linuxserver/wireguard:latest
    container_name: wireguard
    cap_add:
      - NET_ADMIN
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Bogota
      - SERVERURL=<TU_IP_PUBLICA>   # IP pública de tu VM
      - SERVERPORT=51820
      - PEERS=3                     # cuántos clientes vas a necesitar
      - PEERDNS=1.1.1.1,8.8.8.8
      - INTERNAL_SUBNET=10.13.13.0
      - LOG_CONFS=true              # ver nota de seguridad más abajo
    volumes:
      - ./config:/config
      - /lib/modules:/lib/modules
    ports:
      - 51820:51820/udp
    sysctls:
      - net.ipv4.conf.all.src_valid_mark=1
    restart: unless-stopped

Variables clave:

  • SERVERURL: la IP pública (o dominio) de tu VM. Es lo que queda grabado en los .conf de cada peer.
  • PEERS: cuántos clientes/dispositivos vas a conectar. La imagen genera uno por cada número.
  • INTERNAL_SUBNET: la red interna del túnel (cada peer recibe una IP dentro de esta subred).
  • LOG_CONFS: imprime en los logs, al arrancar, el QR de cada peer, que contiene su configuración completa. Útil la primera vez, pero debe desactivarse después (ver sección de hardening).
docker compose up -d

Verificación

Dos comandos para confirmar que todo funciona:

docker logs --tail 50 wireguard
docker exec wireguard wg show

wg show debe mostrar tu interfaz wg0 y, para cada peer conectado, un latest handshake reciente:

interface: wg0
  public key: <...>
  listening port: 51820

peer: <clave-pública-del-peer>
  endpoint: <ip-del-cliente>:51820
  allowed ips: 10.13.13.2/32
  latest handshake: 36 seconds ago
  transfer: 1.79 MiB received, 12.65 MiB sent

Si el handshake no aparece después de conectar un cliente, el problema casi siempre está en el firewall: revisa primero la regla de ingreso UDP 51820 en Oracle Cloud, y después el firewall del sistema operativo.

Configuración de clientes

La imagen genera, dentro de ./config/peer1/, peer2/, etc., un archivo .conf y un .png con el QR correspondiente. Con la app oficial de WireGuard (disponible para macOS, Windows, iOS y Android; en Linux se usa wg-quick desde la terminal) puedes:

  • Escanear el QR directamente desde el móvil, o
  • Importar el .conf en el cliente de escritorio.

Cada peer tiene su propio par de llaves y su propia IP dentro de la subred interna (10.13.13.x).

Comprobar la IP de salida

Con el túnel activo, confirma desde el portátil que tu tráfico realmente sale por la VM:

curl -s https://ifconfig.me

Debe devolver la IP pública de tu VM en Oracle Cloud, no la de la red a la que estás conectado. Si además quieres ver cómo te ubican los servicios, abre en el navegador cualquier sitio de “cuál es mi IP”: debería mostrar Colombia (la ciudad exacta depende de la base de geolocalización que use cada sitio).

Hardening básico

Tres ajustes que apliqué después de la instalación inicial:

  1. Backup de la configuración. La carpeta config/ contiene las llaves privadas de todos los peers. Sin backup, perder esa carpeta significa reconstruir todos los clientes desde cero.

    tar czf wireguard-config-backup-$(date +%F).tar.gz config
    chmod 600 wireguard-config-backup-*.tar.gz
    

    Y sacar esa copia del servidor a un lugar seguro.

  2. Desactivar LOG_CONFS. Con esta variable en true, cada vez que el contenedor arranca imprime en los logs el QR de cada peer, y ese QR contiene la configuración completa, incluida la llave privada. Útil para el primer arranque, pero un riesgo si alguien tiene acceso a esos logs después.

    - LOG_CONFS=false
    
  3. Fijar la versión de la imagen. Usar :latest significa que un docker compose pull futuro puede traer una versión con cambios de comportamiento inesperados. Fijé la imagen exacta (tag + digest) que ya tenía funcionando:

    docker inspect wireguard --format '{{index .Config.Labels "org.opencontainers.image.version"}}'
    docker image inspect lscr.io/linuxserver/wireguard:latest --format '{{index .RepoDigests 0}}'
    

    Con esos dos valores, reemplazo el tag en el compose:

    image: lscr.io/linuxserver/wireguard:<version>@sha256:<digest>
    

    La contrapartida: con la versión fijada ya no recibes parches de seguridad de forma automática. Conviene revisar cada cierto tiempo si hay versiones nuevas y actualizar a propósito.

Troubleshooting real: la red que desapareció

Semanas después de la instalación inicial, el contenedor quedó caído. Al intentar levantarlo de nuevo:

$ docker compose up -d
Error response from daemon: failed to set up container networking: failed to create endpoint wireguard on network wireguard_default: network d2cc557b... does not exist

¿Qué pasó? El contenedor tenía guardada una referencia a una red de Docker (wireguard_default) por su ID interno. Esa red había sido eliminada en algún momento (por ejemplo, con un docker network prune, o al recrear otro stack que compartía el mismo nombre de red). El contenedor seguía intentando conectarse a un ID de red que ya no existía, aunque el nombre siguiera siendo el mismo.

Un detalle que confirma el diagnóstico: docker logs no mostraba nada, porque el contenedor nunca llegaba a arrancar — fallaba antes, a nivel de red.

La solución es simple una vez identificado el problema: eliminar el contenedor y forzar que Compose recree la red desde cero.

docker compose down
docker compose up -d
✔ Network wireguard_default Created
✔ Container wireguard       Started

Y en los logs, confirmación de que el túnel volvió a activarse:

**** Found WG conf /config/wg_confs/wg0.conf, adding to list ****
**** Activating tunnel /config/wg_confs/wg0.conf ****
**** All tunnels are now active ****

Lección: si tu VM reinicia Docker, hace limpieza de redes huérfanas, o reconstruyes otro servicio que use el mismo nombre de red, vale la pena revisar que los contenedores con restart: unless-stopped sigan realmente arrancando, no solo que el daemon de Docker esté corriendo.

Límites que conviene tener claros

  • No es anonimato. La IP de salida está asociada a tu cuenta de Oracle Cloud. La VPN cifra tu tráfico frente a la red local y cambia tu ubicación aparente, pero no te vuelve anónimo.
  • Sigue siendo una IP de datacenter. Aunque sea colombiana, algunos servicios clasifican las IPs de proveedores cloud como “hosting” o “VPN” y podrían pedir verificación de todos modos. En mi caso resolvió el problema, pero no hay garantía de que funcione igual con todos los servicios.
  • Docker se salta el firewall del sistema operativo. Docker enruta los puertos publicados con reglas NAT que se aplican antes de las cadenas que usan firewalls como ufw. Por eso no tuve que abrir nada en el SO, pero también significa que cualquier puerto que publiques en un contenedor queda expuesto aunque el firewall local diga lo contrario. La regla de ingreso en Oracle Cloud es la que realmente filtra.

Resultado: navegar como si estuviera en Bogotá

Con el túnel activo desde cualquier red no confiable, todo mi tráfico sale por la VM en Oracle Cloud. Para cualquier servicio que consulte mi IP pública —mi banco, una tienda en línea colombiana— el origen es una IP de Bogotá, no la red wifi desde la que me conecté.

No es una solución mágica ni reemplaza buenas prácticas de seguridad adicionales, pero resuelve exactamente el problema que tenía: cifrar mi tráfico fuera de casa sin que mis propios servicios colombianos me traten como si estuviera entrando desde el extranjero.