Actualizado el 31 oct 2026
Docker
Si tus servicios se ejecutan con Docker Compose, añade nuestro contenedor al proyecto y serán
accesibles desde internet en tu dirección IPv4 pública dedicada, sin cambiar nada en los propios
servicios. El tráfico entrante llega por el túnel y no a través de un puerto publicado, así que
no tienes que añadir entradas ports:, no necesitas una dirección enrutable de tu operador y no
hay nada que abrir en el router ni en el firewall del host.
Antes de empezar
- Una dirección IP de GetPublicIP y su clave API. Consulta Crear una clave API
- Linux: Docker Engine y Compose v2, es decir,
docker composeen lugar del antiguo scriptdocker-compose - macOS: Docker Desktop, en Intel o Apple Silicon
- Windows: Docker Desktop en modo de contenedores Linux, con un kernel de WSL2 6.6. Compruébalo
con
wsl --versiony ejecutawsl --updatesi muestra una versión anterior, porque a los kernels anteriores a 6.6 les falta un módulo de netfilter que el túnel necesita
Todo lo que hay en esta página funciona sin cambios en Windows y macOS con Docker Desktop, sin instalar nada más que el propio Docker Desktop. Docker Desktop es gratuito para uso personal, educación, proyectos de código abierto no comerciales y pequeñas empresas con menos de 250 empleados y menos de 10 millones de USD de ingresos anuales; las organizaciones más grandes necesitan una suscripción de pago de Docker. Consulta macOS y Windows para ver los detalles de cada uno.
La imagen se publica para linux/amd64 y linux/arm64 en
ghcr.io/getpublicip/getpublicip.
Pon tu clave API en un archivo .env
Crea un archivo .env junto a tu archivo de Compose. Consulta
Crear una clave API para más información.
GETPUBLICIP_API_KEY=your-api-key-from-the-dashboard
Compose lee el archivo .env automáticamente, y por eso los archivos de Compose de abajo hacen
referencia a ${GETPUBLICIP_API_KEY} en lugar de contener la clave. Añade .env a tu
.gitignore.
Añádelo a tu archivo de Docker Compose
Añade este servicio junto a los contenedores que ya ejecutas:
services:
getpublicip:
image: ghcr.io/getpublicip/getpublicip:latest
restart: unless-stopped
environment:
GETPUBLICIP_API_KEY: "${GETPUBLICIP_API_KEY}"
GETPUBLICIP_MAPPINGS: "8080/tcp->web:80; 443/tcp->web:443"
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun
sysctls:
net.ipv4.ip_forward: 1
net.ipv4.conf.all.src_valid_mark: 1
net.ipv6.conf.all.forwarding: 1
networks:
- appnet
Sustituye appnet por la red en la que ya están tus servicios, y web por los nombres de tus
propios servicios. Todo lo demás puede quedarse como está.
Mantén restart: unless-stopped. Es la forma en que el agente se recupera de los fallos que no
puede arreglar sobre la marcha, como se describe en
Caídas de conexión.
Un ejemplo completo que funciona
Si todavía no tienes un proyecto de Compose, este archivo es una configuración completa: un
servidor web en tu propia dirección IP pública, accesible desde cualquier lugar. Guárdalo como
compose.yaml:
services:
web:
image: nginx:alpine
restart: unless-stopped
networks:
- appnet
getpublicip:
image: ghcr.io/getpublicip/getpublicip:latest
restart: unless-stopped
environment:
GETPUBLICIP_API_KEY: "${GETPUBLICIP_API_KEY}"
GETPUBLICIP_MAPPINGS: "80/tcp->web:80"
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun
sysctls:
net.ipv4.ip_forward: 1
net.ipv4.conf.all.src_valid_mark: 1
net.ipv6.conf.all.forwarding: 1
networks:
- appnet
networks:
appnet:
Después, inícialo:
docker compose up -d
Espera entre 30 y 60 segundos y abre http://{YOUR IP ADDRESS} desde cualquier red, o desde tu
teléfono con datos móviles. Si ves la página de bienvenida de nginx, una solicitud desde
internet ha llegado a tu contenedor.
web no tiene ninguna entrada ports:. El agente llega a nginx a través de la red interna
appnet, y los dos servicios se unen a ella porque el agente resuelve web por su nombre de
servicio, algo que solo funciona en una red que compartan ambos. Si quitaras los bloques
networks:, Compose pondría los dos en la red predeterminada del proyecto, y eso funciona igual
de bien, pero darle nombre a la red deja visible el requisito.
Redirige tus puertos
GETPUBLICIP_MAPPINGS decide qué puertos de tu IP pública van a cada uno de tus contenedores.
Separa las entradas con ; o con saltos de línea:
publicPort[/proto[,proto]][/stack[,stack]]->service[:internalPort]
| Parte | Valor predeterminado | Notas |
|---|---|---|
publicPort | Ninguno | un solo puerto, o un rango como 60000-60099 |
proto | tcp | tcp o udp; sepáralos con comas para usar ambos |
stack | ipv4 | ipv4 o ipv6; sepáralos con comas para doble pila |
service | Ninguno | el destino, que se resuelve en tiempo de ejecución: un nombre de servicio de Compose, cualquier nombre de host que se pueda resolver o una dirección IPv4 literal |
internalPort | igual que publicPort | el puerto en el destino; para un rango, omítelo o repite el mismo rango |
| Ejemplo | Significado |
|---|---|
8080/tcp->web:80 | El TCP 8080 de tu IP pública va a web en el puerto 80 |
443/tcp,udp->web:443 | TCP y UDP 443 van a web en el puerto 443 |
53/udp->dns:53 | UDP 53 va a dns en el puerto 53 |
9000/tcp/ipv4,ipv6->api | doble pila; el puerto interno es 9000 de forma predeterminada |
60000-60099/udp->turn | UDP del 60000 al 60099 va a turn en los mismos puertos |
Los segmentos que siguen al puerto se reconocen por palabra clave, así que su orden no importa.
8080/tcp/ipv4 y 8080/ipv4/tcp significan lo mismo.
Refiérete a tus contenedores por su nombre de servicio, nunca por su dirección IP. Compose asigna las IP de los contenedores de forma dinámica, así que el agente resuelve los nombres por su cuenta y los vuelve a comprobar cada 30 segundos. Cuando un contenedor se reinicia con otra IP, el agente actualiza las reglas de reenvío.
Un destino también puede ser cualquier nombre de host que se resuelva o una dirección IPv4 literal, y las tres opciones se comportan igual. No se acepta una dirección IPv6 literal; usa en su lugar un nombre de host con un registro AAAA.
Rangos de puertos
Algunos servicios necesitan un bloque entero de puertos en lugar de uno solo: un servidor TURN para llamadas de voz y videollamadas, FTP en modo pasivo o cualquier cosa que transporte medios RTP. Escribe el puerto público como un rango y cada puerto del rango se redirige al mismo puerto de tu contenedor:
GETPUBLICIP_MAPPINGS: "3478/tcp,udp->turn; 60000-60099/udp->turn"
El puerto 60042 de tu IP pública siempre llega al puerto 60042 de turn. Estos servicios le
indican al otro extremo qué puerto usar, así que un puerto que hubiera cambiado por el camino
rompería la conexión sin ningún error.
Por el mismo motivo, un rango solo puede ir al mismo rango. Puedes omitir el puerto interno,
como arriba, o repetir el rango exactamente (60000-60099/udp->turn:60000-60099). Enviarlo a
puertos distintos, como ->turn:61000-61099, se rechaza al iniciar el contenedor con
a port range must forward to the same range. Un rango tampoco puede incluir el puerto 51820,
que usa el propio túnel.
Abre solo los puertos que tu servicio esté configurado para usar. En un servidor TURN, eso
significa el rango de puertos de relay que tenga configurado, no todo lo que está por encima de
49152. Requiere la imagen 1.3.1 o posterior.
Redirigir IPv6
Las redirecciones son IPv4 salvo que indiques otra cosa, así que añade la pila ipv6 para
redirigir tu dirección IPv6 pública, o ipv4,ipv6 para redirigir ambas. Para que funcione
tienen que cumplirse dos condiciones.
Tu red de Compose necesita tener IPv6 activado. Las redes bridge de Docker son solo IPv4 de
forma predeterminada, así que un contenedor en una de ellas no tiene una dirección IPv6 a la que
el agente pueda reenviar. Añade a la red enable_ipv6: true y una subred IPv6:
networks:
appnet:
enable_ipv6: true
ipam:
config:
- subnet: 172.29.7.0/24
- subnet: fd00:dead:beef::/64
La pila se aplica tanto al destino como al puerto público. Las reglas de reenvío no pueden
traducir entre las dos familias de direcciones, así que una solicitud IPv6 tiene que llegar a tu
contenedor por IPv6. Por eso, 8080/tcp/ipv4,ipv6->web:80 necesita que web tenga tanto una
dirección IPv4 como una IPv6, y eso es lo que le da la red de arriba.
Mantén net.ipv6.conf.all.forwarding: 1 en el bloque sysctls del agente. Ya está en los
ejemplos de esta página, y solo hace falta cuando rediriges IPv6.
Si falta algo, el agente te indica qué destino y qué familia fallaron, con
no destination resolved in the family its mapping asked for. Si una configuración que por lo
demás funciona bien muestra esa línea para una redirección ipv6, casi siempre se trata de una
red sin enable_ipv6.
SSL / TLS
GetPublicIP trabaja a nivel de paquete. Reenvía TCP y UDP a tus contenedores, y el cifrado y los certificados quedan en tus manos: si sirves HTTPS, es tu propio servicio el que termina la conexión TLS. Redirige el 443 al contenedor que ya tiene tu certificado, y tu configuración TLS se queda como está:
GETPUBLICIP_MAPPINGS: "443/tcp->web:443; 80/tcp->web:80"
No desciframos tu tráfico
Tu IP pública no es un proxy TLS. No tiene ninguno de tus certificados ni de tus claves privadas, así que no tiene nada con lo que descifrar. La sesión TLS se negocia directamente entre el navegador de tu visitante y tu propio servicio, y permanece cifrada durante todo el trayecto: al salir del navegador, a través de internet hasta tu IP pública, por el túnel WireGuard y dentro de tu contenedor, donde se descifra por primera vez.
El túnel añade una segunda capa de cifrado, exterior, entre nuestra red y tu máquina, y dentro de ella viaja el mismo texto cifrado TLS que envió tu visitante. Tus certificados y claves privadas nunca salen de tu máquina, y tu tráfico nunca se descifra y se vuelve a cifrar por el camino. No desciframos el tráfico TLS ni tenemos planes de hacerlo.
Tu servicio necesita un certificado válido
Como el certificado está en el servicio que termina la conexión, ese servicio necesita un certificado válido, igual que en cualquier otro host público.
- Apunta un dominio a tu IP pública. Añade un registro
Apara tu dirección IPv4, y un registroAAAAsi también sirves IPv6. Las autoridades de certificación emiten certificados para nombres de dominio, así que una dirección IP sola no basta. - Obtén el certificado de Let's Encrypt. Es gratuito y automatizado. Caddy y Traefik lo solicitan y lo renuevan por ti sin más configuración que tu nombre de dominio; nginx y Apache se combinan con certbot.
- Si usas un desafío HTTP-01, redirige el puerto 80 además del 443 al mismo servicio. Let's Encrypt se conecta al puerto 80 de tu IP pública para validar el desafío, y falla si solo está redirigido el 443. Un desafío DNS-01 no necesita el puerto 80.
- No quites esas redirecciones. La renovación repite el mismo desafío aproximadamente cada 60 días, y falla sin avisar si la redirección ha desaparecido.
El HTTP sin cifrar sigue sin cifrar
Una redirección al puerto 80 que sirve HTTP sin cifrar va sin cifrar entre tu visitante y tu IP pública, exactamente igual que en cualquier otro lugar de internet. El túnel WireGuard solo cubre el tramo entre nuestra red y tu máquina. Si el contenido importa, activa TLS.
Redirigir a un servicio del host
Para hacer público un servicio que se ejecuta directamente en el host de Docker, apunta una
redirección a host.docker.internal:
services:
getpublicip:
environment:
GETPUBLICIP_MAPPINGS: "8081/tcp->host.docker.internal:8081"
extra_hosts:
- "host.docker.internal:host-gateway" # needed on Linux; built in on Docker Desktop
- El servicio del host tiene que escuchar en
0.0.0.0, no en127.0.0.1. Un servicio vinculado a loopback rechaza la conexión que viene del contenedor. Compruébalo conss -lntp. - Un firewall en el host la descartará sin avisar a menos que permitas el tráfico entrante
desde la subred del bridge en
docker0o en la interfazbr-*.
Los destinos en el host y en contenedores se pueden mezclar libremente en un mismo
GETPUBLICIP_MAPPINGS, salvo con
Ver la IP real del cliente,
que no funciona con destinos en el host.
Redes de Docker Compose
Como los destinos se resuelven por nombre de servicio, el contenedor getpublicip tiene que
estar en la misma red de Docker que los contenedores a los que reenvía.
- Una red predeterminada. Funciona tal cual.
- Varias redes. Incluye
getpublicipen cada red que contenga un servicio al que reenvíe. - Destinos en otro proyecto de Compose. Ambos proyectos tienen que unirse a una red externa
compartida:
docker network create shared
Después, en cada archivo de Compose:services: getpublicip: networks: - shared networks: shared: external: true
Si esto está mal, los logs repiten waiting for destinations to resolve y el agente sigue
reintentándolo en lugar de fallar directamente.
Revisa los logs
docker compose up -d
docker compose logs -f getpublicip
Un arranque correcto levanta el túnel en la interfaz pi0, resuelve tus destinos, instala las
reglas de reenvío y envía las redirecciones a GetPublicIP. Después, el agente sigue en marcha y
vuelve a comprobar los destinos y el propio túnel cada 30 segundos.
Un túnel en buen estado escribe en el log una línea como esta. Tenla presente para compararla si el túnel da problemas más adelante:
tunnel status up=true interfaces=1 peers=1 healthy=true handshakeAge=31s rxDelta=0
Fíjate en healthy=true. up=true solo significa que la interfaz existe, y sigue siendo true
incluso en un túnel que ha dejado de pasar tráfico.
Pruebas
Espera unos 30 a 60 segundos antes de probar. Al enviar redirecciones nuevas, tu dirección
IP pasa a un estado updating mientras las aplicamos, y las solicitudes que se hagan en ese
intervalo fallarán. Es normal que haya dos o tres fallos seguidos de un acierto.
Haz la prueba desde una red que no sea la tuya. Lo más fácil es usar datos móviles:
curl http://{YOUR IP ADDRESS}
Caídas de conexión
El túnel se restablece solo tras una caída de la conexión, un reinicio del router o un cambio de dirección por parte de tu operador, sin que tengas que reiniciar ni configurar nada.
La mayoría de las veces se repara solo, sin que el agente tenga que hacer nada. El túnel es una conexión saliente que se mantiene abierta con un keepalive cada 25 segundos, y nuestro servidor vuelve a aprender tu dirección a partir de cualquier paquete que envíe el agente, así que, cuando tu router elimina la entrada NAT o te pasa a un puerto nuevo, el siguiente keepalive la restablece.
Para los fallos menos frecuentes que un keepalive no puede arreglar, el agente se detiene en
lugar de intentar reparar el túnel sobre la marcha, y Docker lo reinicia con una conexión nueva.
En el log verás tunnel appears broken cuando lo detecta por primera vez, y después
exiting so the container is restarted si el problema persiste durante varias comprobaciones.
Las dos líneas forman parte de la recuperación normal.
La recuperación depende de tu política de reinicio, así que mantén restart: unless-stopped en
el servicio. Sin ella, el contenedor se detiene y se queda detenido.
Mientras el host esté sin conexión a internet, tus servicios tampoco serán accesibles. El túnel se recupera en cuanto vuelve a haber conexión.
Ver la IP real del cliente
De forma predeterminada, tus contenedores no ven la dirección IP del visitante. El contenedor de
GetPublicIP reescribe la dirección de origen del tráfico entrante al reenviarlo, así que todos
los visitantes aparecen en tus logs de acceso con la misma dirección IP local. La
geolocalización, la limitación de solicitudes por cliente, el bloqueo de abusos y fail2ban ven
todos una sola dirección IP.
GETPUBLICIP_PRESERVE_CLIENT_IP desactiva esa reescritura, y entonces tus contenedores ven la
dirección IP real del cliente.
Requiere un cambio en cada servicio al que reenvías, así que lee esto antes de activarlo.
Mientras la dirección de origen se reescribe con una de la red de Compose, tus servicios
responden a través del contenedor de GetPublicIP automáticamente. Con
GETPUBLICIP_PRESERVE_CLIENT_IP activado, la ruta predeterminada de cada servicio tiene que
apuntar al contenedor de GetPublicIP, para que sus respuestas vuelvan al cliente a través de tu
dirección IP pública. Sin esa ruta, las respuestas salen por tu conexión a internet normal y el
cliente las descarta.
Requiere la imagen 1.1.0 o posterior.
El ajuste se aplica a todo el agente y no a una sola redirección, así que afecta a todos los servicios a los que reenvía ese agente.
Por qué hay que cambiar la ruta predeterminada
Todo lo demás de esta sección se deriva de lo que le ocurre a una solicitud que entra y a su respuesta, que vuelve a salir:
De forma predeterminada
visitor 203.0.113.9 → your public IP → tunnel → agent
agent rewrites the source to its own address, 172.29.7.2
web replies to 172.29.7.2, which is on its own subnet
agent puts the real address back, reply leaves through the tunnel
Con la preservación de la IP del cliente activada
visitor 203.0.113.9 → your public IP → tunnel → agent
web sees 203.0.113.9 and then send replies to 203.0.113.9
the web container reply will follow the default route
Un contenedor decide adónde enviar una respuesta mirando primero su propia subred y después su ruta predeterminada. Mientras se reescribe la dirección de origen, todas las respuestas van dirigidas a algo de la subred local, y la ruta predeterminada nunca entra en juego. Con la preservación de la IP del cliente activada, todas las respuestas van dirigidas a algún lugar de internet, así que la ruta predeterminada decide sobre todas ellas. La solución es, por tanto, una ruta configurada dentro de cada servicio, y ningún ajuste del agente puede sustituirla.
Docker no tiene una forma integrada de hacerlo, así que los ejemplos de abajo configuran la ruta predeterminada de cada contenedor de servicio con un pequeño contenedor sidecar.
Proyecto de ejemplo
Hay dos versiones del mismo proyecto. Empieza por la de solo IPv4, a menos que también redirijas tu dirección IPv6 pública. Cada línea de IPv6 de abajo es una cosa más que acertar, y una configuración IPv6 a medias te deja un servicio que funciona por IPv4 y se queda colgado por IPv6.
Solo IPv4
services:
getpublicip:
image: ghcr.io/getpublicip/getpublicip:latest
restart: unless-stopped
environment:
GETPUBLICIP_API_KEY: "${GETPUBLICIP_API_KEY}"
GETPUBLICIP_MAPPINGS: "80/tcp->web:80"
GETPUBLICIP_PRESERVE_CLIENT_IP: "true"
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun
sysctls:
net.ipv4.ip_forward: 1
net.ipv4.conf.all.src_valid_mark: 1
networks:
appnet:
ipv4_address: 172.29.7.2 # fixed, because the sidecar routes to it
web:
image: nginx:alpine
restart: unless-stopped
networks:
appnet:
ipv4_address: 172.29.7.3 # give every service a fixed address, not just the agent
# Points web's default route at the agent, from inside web's own network
# namespace. Runs once and exits.
web-route:
image: alpine:3.20
network_mode: "service:web"
cap_add:
- NET_ADMIN
restart: "no"
command: sh -c 'ip route replace default via 172.29.7.2'
depends_on:
web:
condition: service_started
restart: true # re-run this sidecar whenever web restarts
getpublicip:
condition: service_started
networks:
appnet:
driver: bridge
ipam:
config:
- subnet: 172.29.7.0/24
gateway: 172.29.7.1
IPv4 e IPv6
El mismo proyecto con la mitad de IPv6 añadida. Cambian cuatro cosas: la red recibe
enable_ipv6: true y una subred IPv6, cada servicio recibe una ipv6_address fija, el agente
recibe el sysctl net.ipv6.conf.all.forwarding y el sidecar configura una segunda ruta
predeterminada.
services:
getpublicip:
image: ghcr.io/getpublicip/getpublicip:latest
restart: unless-stopped
environment:
GETPUBLICIP_API_KEY: "${GETPUBLICIP_API_KEY}"
GETPUBLICIP_MAPPINGS: "80/tcp/ipv4,ipv6->web:80"
GETPUBLICIP_PRESERVE_CLIENT_IP: "true"
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun
sysctls:
net.ipv4.ip_forward: 1
net.ipv4.conf.all.src_valid_mark: 1
net.ipv6.conf.all.forwarding: 1
networks:
appnet:
ipv4_address: 172.29.7.2 # fixed, because the sidecar routes to it
ipv6_address: fd00:c0de:7::2
web:
image: nginx:alpine
restart: unless-stopped
networks:
appnet:
ipv4_address: 172.29.7.3 # give every service a fixed address, not just the agent
ipv6_address: fd00:c0de:7::3
# Points web's default routes at the agent, from inside web's own network
# namespace. Runs once and exits. Both families need their own route.
web-route:
image: alpine:3.20
network_mode: "service:web"
cap_add:
- NET_ADMIN
restart: "no"
command: >
sh -c 'ip route replace default via 172.29.7.2;
ip -6 route replace default via fd00:c0de:7::2'
depends_on:
web:
condition: service_started
restart: true # re-run this sidecar whenever web restarts
getpublicip:
condition: service_started
networks:
appnet:
driver: bridge
enable_ipv6: true
ipam:
config:
- subnet: 172.29.7.0/24
gateway: 172.29.7.1
- subnet: fd00:c0de:7::/64
gateway: fd00:c0de:7::1
Sea cual sea la versión de la que partas, repite el sidecar para cada servicio al que reenvíes y comprueba los cuatro puntos siguientes. Si falla cualquiera de ellos, el servicio se queda colgado sin mostrar ningún error.
Da una dirección fija a cada servicio de la red, no solo al agente. Un servicio sin
dirección fija toma la dirección libre más baja, que es la que reservaste para el agente, y por
depends_on a menudo arranca primero y se la queda. Entonces el agente ni siquiera llega a
arrancar y muestra Address already in use.
Si rediriges IPv6, configura las dos rutas. La ruta predeterminada de IPv6 es independiente
de la de IPv4, así que un sidecar que solo configura la ruta IPv4 deja que las respuestas IPv6
salgan por el bridge, y esas conexiones se quedan colgadas mientras IPv4 funciona. La segunda
línea ip -6 route replace del ejemplo de doble pila configura esa ruta. Olvidarla es el fallo
más habitual de esa versión, porque todo lo que pruebas por IPv4 funciona.
La ruta no sobrevive a un reinicio de tu servicio. Docker crea un espacio de nombres de red
nuevo con una tabla de rutas limpia, así que la ruta desaparece y el tráfico entrante se detiene
hasta que el sidecar vuelve a ejecutarse. El depends_on de arriba se encarga de esto:
restart: true le indica a Compose que vuelva a ejecutar el sidecar cada vez que web se
reinicie, de modo que tanto docker compose restart web como docker compose up -d reparan la
ruta por sí solos.
No cubre el caso en que Docker reinicia tu servicio por su cuenta. Si web se cae, o lo mata el
out-of-memory killer, su propia política restart: lo vuelve a levantar sin que intervenga
Compose, y nada vuelve a ejecutar el sidecar. Falta la ruta, el tráfico entrante se detiene y
nada avisa de un problema. Restablécela con:
docker compose up -d web-route
Recomendamos añadir la ruta predeterminada en el Dockerfile del servicio para que sobreviva a
estos reinicios. Si no, comprueba las rutas con docker exec web ip route y vuelve a configurar
la ruta predeterminada cada vez que haya desaparecido.
Los destinos en el host no pueden funcionar en este modo. Reenviar a un servicio del host de Docker requiere también que la respuesta vuelva a través del agente, y eso implicaría cambiar la ruta predeterminada del propio host.
Comprueba que funciona
Haz una solicitud a tu dirección IP:
curl http://{YOUR IP ADDRESS} # one request through your public IP
docker compose logs --tail 10 web # what your container recorded
El log debería mostrar la dirección IP real del cliente. Si muestra 172.29.7.2, la
preservación de la IP del cliente no está activada y estás viendo el comportamiento
predeterminado.
Hacer la prueba desde el propio host de Docker no es problema. La solicitud sigue saliendo hacia tu IP pública, llega a nuestra red y vuelve por el túnel, así que tu contenedor registra la dirección pública de tu conexión a internet, fácil de distinguir de la dirección del bridge que habría puesto la reescritura.
Si rediriges IPv6, compruébalo por separado con curl -6. Las dos familias tienen rutas
predeterminadas distintas, así que una puede funcionar mientras la otra está rota.
Cuando no funciona
Una ruta predeterminada mal configurada no produce ningún error. Las conexiones se quedan
colgadas, el túnel sigue activo y sigue registrando healthy=true, las reglas de reenvío siguen
siendo correctas y nada en docker compose logs getpublicip informa de un problema, porque desde
el punto de vista del agente no lo hay. Las respuestas de tu contenedor salen por el camino
equivocado en lugar de a través de tu dirección IP pública.
Revisa la tabla de rutas dentro del servicio:
docker exec web ip route # default must be via the agent's address
docker exec web ip -6 route # and this one too, if you forward IPv6
Enviar el tráfico saliente por tu IP
Las secciones anteriores tratan del tráfico entrante. GETPUBLICIP_OUTBOUND cubre la otra
dirección: tus contenedores salen a internet desde tu IP pública en lugar de desde la dirección
de tu operador. Es útil cuando algo a lo que te conectas solo permite una dirección conocida, o
cuando quieres que tu tráfico saliente venga de la misma dirección que el entrante.
Necesita el mismo cambio de ruta predeterminada que la preservación de la IP del cliente, por el mismo motivo: la respuesta tiene que volver a través del agente. Si ya configuraste el sidecar para eso, solo tienes que añadir el ajuste.
services:
getpublicip:
image: ghcr.io/getpublicip/getpublicip:latest
restart: unless-stopped
environment:
GETPUBLICIP_API_KEY: "${GETPUBLICIP_API_KEY}"
GETPUBLICIP_MAPPINGS: "80/tcp->web:80"
GETPUBLICIP_OUTBOUND: "true"
cap_add:
- NET_ADMIN
devices:
- /dev/net/tun
sysctls:
net.ipv4.ip_forward: 1
net.ipv4.conf.all.src_valid_mark: 1
net.ipv6.conf.all.forwarding: 1
networks:
appnet:
ipv4_address: 172.29.7.2
Después, apunta al agente cada contenedor que deba usarlo, exactamente igual que en el Proyecto de ejemplo de arriba. Un contenedor cuya ruta no toques sigue usando el bridge normal de Docker, así que puedes pasar los servicios de uno en uno.
Compruébalo desde dentro de un contenedor que hayas enrutado:
docker exec web curl -s https://api.getpublicip.com/ip
Debería mostrar tu dirección de GetPublicIP, no la de tu casa ni la de tu oficina.
Solo tráfico saliente, sin puertos redirigidos
Si lo único que quieres es la dirección saliente, GETPUBLICIP_MAPPINGS pasa a ser
opcional:
environment:
GETPUBLICIP_API_KEY: "${GETPUBLICIP_API_KEY}"
GETPUBLICIP_OUTBOUND: "true"
Un agente iniciado así deja las redirecciones de puertos de tu dirección IP como están, incluidas las que hayas configurado en el panel.
Lo que debes saber antes de activarlo
Tus contenedores comparten una dirección. Todo lo que hay detrás del agente sale desde la misma dirección IPv4 pública y la misma IPv6 pública, así que el otro extremo no puede distinguir un contenedor de otro.
Falla en modo cerrado. Un contenedor enrutado a través del agente no tiene internet en absoluto mientras el túnel esté caído, cuando antes habría usado tu conexión normal. Es intencionado, para que tu tráfico nunca vuelva a salir desde la dirección de tu operador sin que lo sepas, pero piénsalo antes de enrutar a través del agente algo de lo que dependes.
Si un contenedor no puede salir a internet, el problema casi siempre está en su ruta y no en el agente. Compruébalo dentro del propio contenedor:
docker exec web ip route # default must be via the agent's address
docker exec web ip -6 route # and this one too, if you use IPv6
Permisos del contenedor
| Ajuste | Por qué hace falta |
|---|---|
cap_add: NET_ADMIN | Configurar la interfaz WireGuard y las reglas de reenvío |
devices: /dev/net/tun | El propio dispositivo del túnel |
net.ipv4.ip_forward=1 | Reenviar el tráfico del túnel a tu contenedor de destino |
net.ipv4.conf.all.src_valid_mark=1 | Lo requiere la configuración de rutas de wg-quick |
net.ipv6.conf.all.forwarding=1 | Solo hace falta si rediriges IPv6 |
macOS
Usa Docker Desktop y el archivo de Compose de arriba funciona sin cambios, tanto en Intel como en Apple Silicon.
Otros entornos de Docker para macOS (Colima, OrbStack, Rancher Desktop) usan kernels distintos y no se han verificado, así que no podemos decir si el túnel se levanta en ellos.
Windows
El archivo de Compose de arriba no necesita cambios en Windows con Docker Desktop.
Docker Desktop necesita Windows 10 u 11 de 64 bits, en una versión que Microsoft siga admitiendo, y el backend WSL 2, que es el predeterminado. Después, hay dos cosas que comprobar antes de empezar.
1. Docker Desktop tiene que estar en modo de contenedores Linux. El motor de contenedores Windows no puede ejecutar esta imagen y, de todos modos, no está disponible con el backend WSL 2.
2. Tu kernel de WSL2 tiene que ser 6.6.x. A los kernels anteriores, entre ellos el 5.15,
les falta un módulo de netfilter que el túnel necesita, y no hay forma de solucionarlo desde
dentro del contenedor. El túnel no llega a levantarse y muestra
unknown option '--save-mark'. Compruébalo y actualiza desde PowerShell:
wsl --version # need "Kernel version: 6.6.x"
wsl --update # if it reports anything older
La 6.6 no se consolidó como versión predeterminada hasta, aproximadamente, WSL 2.4 a 2.7, así que un equipo que lleve tiempo sin actualizarse puede seguir en la 5.15. El mismo requisito se aplica si ejecutas Docker CE dentro de una distribución de WSL2 en lugar de Docker Desktop, porque comparte el mismo kernel.
En la 6.6, tun es un módulo y no viene integrado en el kernel, así que comprueba que el nodo
de dispositivo existe antes de buscar el problema en el agente. Este comando falla al crear el
contenedor si falta:
docker run --rm --device /dev/net/tun alpine ls -l /dev/net/tun
No hace falta ninguna regla del Firewall de Windows ni reenviar ningún puerto. El tráfico entrante llega por el túnel, así que ni el Firewall de Windows ni el NAT de WSL están en el camino.
Portátiles y suspensión
La suspensión corta el túnel. En macOS y Windows, la máquina virtual Linux se suspende cuando lo hace tu equipo, y tu IP pública deja de ser accesible hasta que se reactiva; en ese momento el túnel se restablece automáticamente. Pruébalo en tu portátil si quieres, pero ejecútalo en un servidor: una máquina que se suspende es un mal host para una IP pública que tiene que estar siempre disponible.
Una clave API, un túnel
Una clave de WireGuard pertenece a un único extremo. Si dos contenedores se ejecutan con la misma clave API, se desconectarán el uno al otro. Gana el que haya hecho el handshake más reciente, y el otro descarta tráfico sin avisar hasta su siguiente keepalive. Parece una inestabilidad aleatoria en lugar de un error claro. La causa habitual es probar en un portátil mientras un servidor ya está conectado con la misma clave.
Este caso se diagnostica solo: los dos contenedores se reinician una y otra vez, porque cada uno concluye que el averiado es él. Un contenedor que se reinicia en bucle en una máquina cuya conexión a internet funciona bien casi siempre significa que un segundo agente está usando la misma clave.
Usa una clave API por cada túnel en funcionamiento.
Solución de problemas
docker compose logs -f getpublicip es el punto de partida.
| Lo que ves | Qué significa |
|---|---|
GETPUBLICIP_API_KEY is required | La variable no está definida, o no se está leyendo tu archivo .env |
tunnel up: … 401 | La clave API no es válida |
waiting for destinations to resolve | El destino todavía no está en marcha, el nombre del servicio es incorrecto o el agente no comparte ninguna red con él |
Errores de wg-quick o de permisos | Falta NET_ADMIN o /dev/net/tun |
iptables-restore … unknown option '--save-mark' | Solo en Windows: un kernel de WSL2 anterior a 6.6. Ejecuta wsl --update. Consulta Windows |
error gathering device information … /dev/net/tun | El nodo de dispositivo no existe en el host. En Docker Desktop, comprueba la versión de tu kernel de WSL2 |
| El tráfico llega, pero las respuestas se quedan colgadas | Los sysctls de reenvío no están configurados |
| Se rechaza una redirección al host | El servicio del host está vinculado a 127.0.0.1 en lugar de a 0.0.0.0, o un firewall del host está descartando la subred del bridge |
tunnel appears broken | Se ha caído la conexión. El agente ya se está ocupando de ello. Consulta Caídas de conexión |
tunnel health cannot be determined | Esta vez el agente no pudo leer el estado del túnel, lo cual no significa que el túnel esté caído. Se le da más tiempo para resolverse solo antes de reiniciar el contenedor |
exiting so the container is restarted | Recuperación normal, no un error. El contenedor vuelve con una conexión nueva. Si se repite durante mucho tiempo, o la máquina no tiene internet o dos agentes comparten una misma clave API |
Nada en el log parece incorrecto, pero el tráfico entrante se queda colgado y GETPUBLICIP_PRESERVE_CLIENT_IP está activado | El destino no envía sus respuestas de vuelta a través del agente. Ejecuta docker exec <target> ip route: la ruta predeterminada tiene que apuntar al agente, y también ip -6 route si rediriges IPv6. Consulta Ver la IP real del cliente |
these destinations cannot work while preserving client IPs | Hay un destino en el host redirigido mientras la preservación de la IP del cliente está activada, y eso no puede funcionar. Pasa esas redirecciones a un segundo agente que deje el ajuste desactivado |
a port range must forward to the same range | Una redirección de rango apunta a puertos distintos en el destino, o hay un rango en un lado y un solo puerto en el otro. Omite el puerto interno o repite el mismo rango. Consulta Rangos de puertos |
no destination resolved in the family its mapping asked for | Una redirección ipv6 que apunta a un servicio sin dirección IPv6, normalmente una red de Compose sin enable_ipv6: true. Consulta Redirigir IPv6 |
Un contenedor que enrutaste a través del agente no tiene internet y GETPUBLICIP_OUTBOUND está activado | O el túnel está caído (este modo falla en modo cerrado a propósito) o la ruta predeterminada del contenedor no apunta al agente. Ejecuta docker exec <target> ip route, y también ip -6 route si usas IPv6. Consulta Enviar el tráfico saliente por tu IP |
GETPUBLICIP_MAPPINGS is required (or set GETPUBLICIP_OUTBOUND=true for egress with no inbound ports) | Iniciaste el agente sin redirecciones de puertos. Eso solo se permite en una configuración solo de salida, que necesita GETPUBLICIP_OUTBOUND=true |
skipping API push: no mappings configured (egress-only) | No es un error. Un agente solo de salida deja intactas las redirecciones de puertos de tu dirección IP en lugar de borrarlas |
preserveClientIp and masqueradeOutbound are mutually exclusive | Se activaron a la vez GETPUBLICIP_PRESERVE_CLIENT_IP y GETPUBLICIP_MASQUERADE. Quita GETPUBLICIP_MASQUERADE |
is not a boolean (use true or false) | Un ajuste como GETPUBLICIP_PRESERVE_CLIENT_IP recibió un valor distinto de true o false. yes y on se rechazan a propósito, para que una errata no pueda dejar el ajuste desactivado sin que te des cuenta |
Para comprobar una configuración sin levantar nada ni contactar con la API, ejecútala en modo de simulación (dry run). Muestra las reglas que aplicaría y termina:
docker run --rm \
-e GETPUBLICIP_API_KEY=x -e GETPUBLICIP_DRY_RUN=true \
-e GETPUBLICIP_ISOLATED_INTERFACE=eth0 \
-e GETPUBLICIP_MAPPINGS="8080/tcp->web:80" \
ghcr.io/getpublicip/getpublicip:latest
Preguntas frecuentes
¿Tengo que publicar puertos en mis contenedores para usar GetPublicIP?
No. El tráfico llega por el túnel WireGuard, no por un puerto publicado en el host, así que los servicios que expones no necesitan entradas ports: ni ningún otro cambio. Añadir ports: expondría también el servicio en tu red local, y normalmente eso no es lo que quieres.
¿El contenedor de GetPublicIP tiene que ejecutarse en modo privilegiado?
No. El contenedor necesita la capacidad NET_ADMIN, el dispositivo /dev/net/tun y los sysctls de reenvío, y nada más.
¿Tengo que instalar el módulo de kernel de WireGuard en el host?
No. La imagen usa el módulo de kernel del host cuando está disponible y, si no, recurre a wireguard-go, la implementación en espacio de usuario que incluye. No hay nada que instalar en el host en ningún caso, en ninguna plataforma.
¿Funciona en macOS y Windows?
Sí, y el archivo de Compose no necesita cambios en ninguno de los dos. El host de tu contenedor es una máquina virtual Linux y no el sistema operativo de escritorio, así que el dispositivo del túnel lo proporciona esa máquina virtual y no hay nada que instalar en macOS ni en Windows. macOS funciona sin cambios en Intel y Apple Silicon con Docker Desktop. En Windows, Docker Desktop tiene que estar en modo de contenedores Linux y tu kernel de WSL2 tiene que ser 6.6.x. Compruébalo con wsl --version y ejecuta wsl --update si muestra una versión anterior, porque a los kernels anteriores a 6.6 les falta un módulo de netfilter que el túnel necesita.
¿GetPublicIP descifra mi tráfico HTTPS?
No. GetPublicIP reenvía paquetes y nunca termina TLS, así que no tiene ninguno de tus certificados ni de tus claves privadas, ni nada con lo que descifrar. La sesión TLS se negocia directamente entre el navegador de tu visitante y el servicio tuyo que la termine, y se descifra por primera vez al llegar allí. Ese servicio necesita su propio certificado válido, igual que en cualquier otro host público. Let's Encrypt es gratuito y automatizado, y encaja bien aquí; si usas un desafío HTTP-01, recuerda redirigir el puerto 80 además del 443.
¿Puedo redirigir un puerto a un servicio que se ejecuta en el host de Docker en lugar de en un contenedor?
Sí. Apunta la redirección a host.docker.internal y, en Linux, añade extra_hosts: - "host.docker.internal:host-gateway". En Docker Desktop viene incluido. También funciona una dirección IP literal. Además, el servicio del host tiene que escuchar en 0.0.0.0 y no en 127.0.0.1, y un firewall en el host tiene que permitir el tráfico entrante desde la subred del bridge. Los destinos en el host son lo único que no se puede combinar con GETPUBLICIP_PRESERVE_CLIENT_IP.
¿Pueden mis contenedores ver la dirección IP real del visitante?
Sí, con GETPUBLICIP_PRESERVE_CLIENT_IP=true, y viene desactivado de forma predeterminada porque requiere trabajo por tu parte. Normalmente, el agente reescribe la dirección de origen del tráfico entrante con la suya propia para que las respuestas vuelvan a través de él, y por eso tus logs de acceso muestran la misma dirección para todos los visitantes. Desactivar esa reescritura significa que cada servicio al que reenvías tiene que enviar sus respuestas de vuelta a través del agente, lo que implica cambiar la ruta predeterminada de ese servicio. Se aplica a todo tu archivo de Compose y no a una sola redirección, y mientras está activado no se puede reenviar a un servicio del host de Docker. Configúralo siempre junto con la ruta, nunca por separado: la variable por sí sola te deja con un servicio que se queda colgado mientras los logs siguen pareciendo correctos.
¿Puedo usar X-Forwarded-For en lugar de GETPUBLICIP_PRESERVE_CLIENT_IP?
Por sí solo, no. X-Forwarded-For solo sirve si lo que añade la cabecera puede ver la dirección real del cliente desde el principio. La dirección de origen se reescribe a nivel de paquete, antes de que nada lea una solicitud HTTP, así que un proxy inverso dentro de tu archivo de Compose recibe paquetes que ya dicen venir del contenedor de GetPublicIP y no tiene nada verdadero que poner en la cabecera. Añadir uno ahí solo registra la dirección equivocada en un segundo lugar. Hay un caso en el que la cabecera sí funciona y no necesitas nada de esto: si tu tráfico pasa por una CDN o un proxy externo como Cloudflare antes de llegar a tu IP pública, ese servicio ve al cliente real y añade la cabecera él mismo, y tus contenedores deberían leerla de la forma habitual.
¿Pueden mis contenedores usar también mi IP pública para el tráfico saliente?
Sí, con GETPUBLICIP_OUTBOUND=true. De forma predeterminada, solo el tráfico entrante usa el túnel y tus contenedores siguen saliendo a internet por tu conexión normal. Al activarlo, su tráfico saliente también pasa por el túnel, de modo que sale desde tu dirección de GetPublicIP. Necesita el mismo cambio que ver la IP real del cliente (la ruta predeterminada de cada contenedor tiene que apuntar al contenedor de GetPublicIP), así que, si ya lo tienes configurado, solo te queda añadir el ajuste. Todos los contenedores que hay detrás comparten la misma dirección. Además, falla en modo cerrado a propósito: mientras el túnel está caído, esos contenedores no tienen internet en absoluto, en lugar de volver a la dirección de tu operador.
¿Qué pasa si se cae mi conexión a internet?
El túnel se restablece solo, y no hay nada que reiniciar ni que configurar. La mayoría de las caídas las repara el keepalive que mantiene abierta la conexión, sin que el agente tenga que intervenir; en un túnel real medimos que el tráfico volvía a fluir unos 17 segundos después de recuperarse la conectividad. Para los fallos menos frecuentes que un keepalive no puede arreglar, el agente detiene el contenedor en lugar de intentar reparar el túnel sobre la marcha, y tu política de reinicio lo vuelve a levantar enseguida con una conexión nueva; por eso el fragmento de Compose usa restart: unless-stopped. Mientras tu internet esté caído, tu servicio no es accesible, y el túnel se recupera en cuanto vuelve a haber conexión.
¿Qué pasa cuando uno de mis contenedores se reinicia con una dirección IP nueva?
El agente vuelve a resolver el nombre de servicio de cada destino a intervalos regulares (GETPUBLICIP_RESOLVE_INTERVAL, 30 segundos de forma predeterminada) y vuelve a aplicar las reglas de reenvío si un contenedor ha cambiado de dirección. No tienes que reiniciar nada. Es normal que se pierda una sola solicitud justo después de una resincronización, mientras se estabiliza el seguimiento de conexiones.
¿Puedo usar la misma clave API en dos lugares a la vez?
No. Una clave de WireGuard pertenece a un único extremo, así que dos contenedores que usan la misma clave API se desconectan el uno al otro. Gana el que haya hecho el handshake más reciente. Se manifiesta como fallos aleatorios e intermitentes en lugar de un error claro, aunque hay una señal inequívoca: los dos contenedores se reinician una y otra vez, porque cada uno concluye que el averiado es él. Usa una clave API por cada túnel en funcionamiento.
Primeros pasos explica cómo funciona el servicio. Para ejecutar tus servicios directamente en un host en lugar de en contenedores, consulta las guías de Linux y Windows.