[{"data":1,"prerenderedAt":1941},["ShallowReactive",2],{"docs-slug-navigation":3,"content-paths":31,"doc-docker":63,"docs-navigation":1930},[4,9,14,19,25],{"path":5,"navigation":6},"\u002Fdocs\u002Fgetting-started",{"title":7,"icon":8},"Getting Started","mdi-file-document-outline",{"path":10,"navigation":11},"\u002Fdocs\u002Flinux",{"title":12,"icon":13},"Linux","mdi-linux",{"path":15,"navigation":16},"\u002Fdocs\u002Fwindows",{"title":17,"icon":18},"Windows","mdi-microsoft-windows",{"path":20,"navigation":21},"\u002Fdocs\u002Fdocker",{"title":22,"icon":23,"description":24},"Docker","mdi-docker","Expose your Docker Compose services to the internet through a GetPublicIP tunnel.",{"path":26,"navigation":27},"\u002Fdocs\u002Fcreate-api-key",{"title":28,"icon":29,"description":30},"Create API Key","mdi-key","Create and manage an API key so tools can configure one of your IP addresses for you.",[32,33,34,35,36,37,39,41,43,45,47,49,51,53,55,57,59,61],{"path":5},{"path":10},{"path":15},{"path":20},{"path":26},{"path":38},"\u002Fguides\u002Ffind-your-public-ip-address\u002Ffind-your-ip-address-on-linux",{"path":40},"\u002Fguides\u002Ffind-your-public-ip-address\u002Ffind-your-ip-address-on-windows",{"path":42},"\u002Fguides\u002Ffind-your-public-ip-address",{"path":44},"\u002Fguides\u002Fself-hosting\u002Fexpose-home-server-to-internet",{"path":46},"\u002Fguides\u002Fself-hosting\u002Fgetpublicip-vs-cloudflare-tunnel-vs-tailscale",{"path":48},"\u002Fguides\u002Fself-hosting\u002Fport-forwarding-not-working",{"path":50},"\u002Fguides\u002Fself-hosting\u002Fself-hosting-behind-carrier-grade-nat-cgnat",{"path":52},"\u002Fguides\u002Fself-hosting\u002Fwireguard-simple-watchdog-script",{"path":54},"\u002Fguides\u002Fstatic-public-ip-address\u002Fhow-to-get-a-public-ip-address",{"path":56},"\u002Fguides\u002Fstatic-public-ip-address\u002Fhow-to-get-a-static-ip-address",{"path":58},"\u002Fguides\u002Fstatic-public-ip-address",{"path":60},"\u002Fguides\u002Fuse-cases\u002Fhome-server",{"path":62},"\u002Fguides\u002Fuse-cases\u002Foffice-server",{"id":64,"title":65,"body":66,"createdAt":1904,"description":1905,"extension":1906,"faq":1907,"meta":1924,"navigation":1925,"path":20,"seo":1926,"stem":1927,"updatedAt":1928,"__hash__":1929},"docs\u002Fdocs\u002F4.docker.md","GetPublicIP - Setting up with Docker",{"type":67,"value":68,"toc":1879},"minimark",[69,73,87,90,95,168,182,196,200,211,237,250,254,257,422,433,445,450,457,648,651,670,677,696,700,710,718,815,879,889,895,898,902,909,923,927,930,933,937,940,974,978,981,985,992,1042,1045,1077,1082,1086,1093,1200,1207,1211,1241,1248,1251,1257,1271,1275,1286,1289,1308,1311,1314,1317,1328,1334,1337,1341,1409,1412,1419,1422,1425,1431,1434,1440,1450,1467,1470,1477,1486,1489,1493,1499,1503,1509,1512,1515,1519,1525,1666,1669,1728,1732,1750,1765,1778,1793,1802,1823,1835,1848,1857,1860,1875],[70,71,22],"h1",{"id":72},"docker",[74,75,76,77,81,82,86],"p",{},"Run your services in Docker Compose? Add one container to your project and your services\nbecome reachable from the internet on a dedicated public IPv4 address, ",[78,79,80],"strong",{},"without changing\nthose services at all",". You do not add ",[83,84,85],"code",{},"ports:"," entries, you do not need a routable address\nfrom your ISP, and there is nothing to open on your router or your host firewall: inbound\ntraffic arrives over the tunnel rather than through a published port.",[88,89],"docs-toc",{},[91,92,94],"h2",{"id":93},"before-you-start","Before you start",[96,97,98,112,130,143],"ul",{},[99,100,101,102,105,106],"li",{},"A GetPublicIP IP address and its ",[78,103,104],{},"API key"," — see\n",[107,108,111],"a",{"className":109,"href":26},[110],"link","Create an API Key",[99,113,114,117,118,121,122,125,126,129],{},[78,115,116],{},"Linux:"," Docker Engine and ",[78,119,120],{},"Compose v2",", meaning ",[83,123,124],{},"docker compose"," rather than the older\n",[83,127,128],{},"docker-compose"," script",[99,131,132,135,136,142],{},[78,133,134],{},"macOS:"," ",[107,137,141],{"className":138,"href":139,"target":140},[110],"https:\u002F\u002Fwww.docker.com\u002Fproducts\u002Fdocker-desktop\u002F","_blank","Docker Desktop",", on either Intel or Apple Silicon",[99,144,145,135,148,151,152,155,156,159,160,163,164,167],{},[78,146,147],{},"Windows:",[107,149,141],{"className":150,"href":139,"target":140},[110]," in ",[78,153,154],{},"Linux containers"," mode, on a ",[78,157,158],{},"WSL2 6.6 kernel",". Check\nwith ",[83,161,162],{},"wsl --version",", and run ",[83,165,166],{},"wsl --update"," if it reports anything older, because kernels\nbefore 6.6 are missing a netfilter module the tunnel needs",[74,169,170,171,176,177,181],{},"Everything on this page works unchanged on Windows and macOS with Docker Desktop, with nothing\nto install beyond Docker Desktop itself. It is free for personal use, education,\nnon-commercial open source, and small businesses with fewer than 250 employees and under $10M\nin annual revenue; larger organisations need a paid subscription from Docker. See\n",[107,172,175],{"className":173,"href":174},[110],"#macos","macOS"," and ",[107,178,17],{"className":179,"href":180},[110],"#windows"," for the\ndetail on each.",[74,183,184,185,176,188,191,192,195],{},"The image is published for ",[83,186,187],{},"linux\u002Famd64",[83,189,190],{},"linux\u002Farm64"," at\n",[83,193,194],{},"ghcr.io\u002Fgetpublicip\u002Fgetpublicip",".",[91,197,199],{"id":198},"put-your-api-key-in-a-env-file","Put your API key in a .env file",[74,201,202,203,206,207,210],{},"Create a ",[83,204,205],{},".env"," file next to your compose file. See\n",[107,208,111],{"className":209,"href":26},[110]," for more information.",[212,213,218],"pre",{"className":214,"code":215,"language":216,"meta":217,"style":217},"language-bash shiki shiki-themes monokai","GETPUBLICIP_API_KEY=your-api-key-from-the-dashboard\n","bash","",[83,219,220],{"__ignoreMap":217},[221,222,225,229,233],"span",{"class":223,"line":224},"line",1,[221,226,228],{"class":227},"sCdxs","GETPUBLICIP_API_KEY",[221,230,232],{"class":231},"s8I7P","=",[221,234,236],{"class":235},"s_Ekj","your-api-key-from-the-dashboard\n",[74,238,239,240,243,244,246,247,195],{},"Compose reads that file automatically, which is why the compose files below refer to\n",[83,241,242],{},"${GETPUBLICIP_API_KEY}"," rather than containing the key itself. Add ",[83,245,205],{}," to your\n",[83,248,249],{},".gitignore",[91,251,253],{"id":252},"add-to-your-docker-compose-file","Add to your Docker compose file",[74,255,256],{},"Add this service alongside the containers you already run:",[212,258,262],{"className":259,"code":260,"language":261,"meta":217,"style":217},"language-yaml shiki shiki-themes monokai","services:\n  getpublicip:\n    image: ghcr.io\u002Fgetpublicip\u002Fgetpublicip:latest\n    restart: unless-stopped\n    environment:\n      GETPUBLICIP_API_KEY: \"${GETPUBLICIP_API_KEY}\"\n      GETPUBLICIP_MAPPINGS: \"8080\u002Ftcp->web:80; 443\u002Ftcp->web:443\"\n    cap_add:\n      - NET_ADMIN\n    devices:\n      - \u002Fdev\u002Fnet\u002Ftun\n    sysctls:\n      net.ipv4.ip_forward: 1\n      net.ipv4.conf.all.src_valid_mark: 1\n      net.ipv6.conf.all.forwarding: 1\n    networks:\n      - appnet\n","yaml",[83,263,264,272,280,292,303,311,322,333,341,350,358,366,374,386,396,406,414],{"__ignoreMap":217},[221,265,266,269],{"class":223,"line":224},[221,267,268],{"class":231},"services",[221,270,271],{"class":227},":\n",[221,273,275,278],{"class":223,"line":274},2,[221,276,277],{"class":231},"  getpublicip",[221,279,271],{"class":227},[221,281,283,286,289],{"class":223,"line":282},3,[221,284,285],{"class":231},"    image",[221,287,288],{"class":227},": ",[221,290,291],{"class":235},"ghcr.io\u002Fgetpublicip\u002Fgetpublicip:latest\n",[221,293,295,298,300],{"class":223,"line":294},4,[221,296,297],{"class":231},"    restart",[221,299,288],{"class":227},[221,301,302],{"class":235},"unless-stopped\n",[221,304,306,309],{"class":223,"line":305},5,[221,307,308],{"class":231},"    environment",[221,310,271],{"class":227},[221,312,314,317,319],{"class":223,"line":313},6,[221,315,316],{"class":231},"      GETPUBLICIP_API_KEY",[221,318,288],{"class":227},[221,320,321],{"class":235},"\"${GETPUBLICIP_API_KEY}\"\n",[221,323,325,328,330],{"class":223,"line":324},7,[221,326,327],{"class":231},"      GETPUBLICIP_MAPPINGS",[221,329,288],{"class":227},[221,331,332],{"class":235},"\"8080\u002Ftcp->web:80; 443\u002Ftcp->web:443\"\n",[221,334,336,339],{"class":223,"line":335},8,[221,337,338],{"class":231},"    cap_add",[221,340,271],{"class":227},[221,342,344,347],{"class":223,"line":343},9,[221,345,346],{"class":227},"      - ",[221,348,349],{"class":235},"NET_ADMIN\n",[221,351,353,356],{"class":223,"line":352},10,[221,354,355],{"class":231},"    devices",[221,357,271],{"class":227},[221,359,361,363],{"class":223,"line":360},11,[221,362,346],{"class":227},[221,364,365],{"class":235},"\u002Fdev\u002Fnet\u002Ftun\n",[221,367,369,372],{"class":223,"line":368},12,[221,370,371],{"class":231},"    sysctls",[221,373,271],{"class":227},[221,375,377,380,382],{"class":223,"line":376},13,[221,378,379],{"class":231},"      net.ipv4.ip_forward",[221,381,288],{"class":227},[221,383,385],{"class":384},"s7s5_","1\n",[221,387,389,392,394],{"class":223,"line":388},14,[221,390,391],{"class":231},"      net.ipv4.conf.all.src_valid_mark",[221,393,288],{"class":227},[221,395,385],{"class":384},[221,397,399,402,404],{"class":223,"line":398},15,[221,400,401],{"class":231},"      net.ipv6.conf.all.forwarding",[221,403,288],{"class":227},[221,405,385],{"class":384},[221,407,409,412],{"class":223,"line":408},16,[221,410,411],{"class":231},"    networks",[221,413,271],{"class":227},[221,415,417,419],{"class":223,"line":416},17,[221,418,346],{"class":227},[221,420,421],{"class":235},"appnet\n",[74,423,424,425,428,429,432],{},"Replace ",[83,426,427],{},"appnet"," with the network your own services are already on, and ",[83,430,431],{},"web"," with your own\nservice names. Everything else can stay as it is.",[74,434,435,436,439,440,195],{},"Keep ",[83,437,438],{},"restart: unless-stopped",". It is not boilerplate. It is how the agent recovers from the\nfaults it cannot fix in place, as described in\n",[107,441,444],{"className":442,"href":443},[110],"#connection-drops","Connection drops",[446,447,449],"h3",{"id":448},"a-complete-working-example","A complete working example",[74,451,452,453,456],{},"Nothing to add it to yet? This is a whole setup in one file: a web server on your own public\nIP address, reachable from anywhere. Save it as ",[83,454,455],{},"compose.yaml",":",[212,458,460],{"className":259,"code":459,"language":261,"meta":217,"style":217},"services:\n  web:\n    image: nginx:alpine\n    restart: unless-stopped\n    networks:\n      - appnet\n\n  getpublicip:\n    image: ghcr.io\u002Fgetpublicip\u002Fgetpublicip:latest\n    restart: unless-stopped\n    environment:\n      GETPUBLICIP_API_KEY: \"${GETPUBLICIP_API_KEY}\"\n      GETPUBLICIP_MAPPINGS: \"80\u002Ftcp->web:80\"\n    cap_add:\n      - NET_ADMIN\n    devices:\n      - \u002Fdev\u002Fnet\u002Ftun\n    sysctls:\n      net.ipv4.ip_forward: 1\n      net.ipv4.conf.all.src_valid_mark: 1\n      net.ipv6.conf.all.forwarding: 1\n    networks:\n      - appnet\n\nnetworks:\n  appnet:\n",[83,461,462,468,475,484,492,498,504,510,516,524,532,538,546,555,561,567,573,579,586,595,604,613,620,627,632,640],{"__ignoreMap":217},[221,463,464,466],{"class":223,"line":224},[221,465,268],{"class":231},[221,467,271],{"class":227},[221,469,470,473],{"class":223,"line":274},[221,471,472],{"class":231},"  web",[221,474,271],{"class":227},[221,476,477,479,481],{"class":223,"line":282},[221,478,285],{"class":231},[221,480,288],{"class":227},[221,482,483],{"class":235},"nginx:alpine\n",[221,485,486,488,490],{"class":223,"line":294},[221,487,297],{"class":231},[221,489,288],{"class":227},[221,491,302],{"class":235},[221,493,494,496],{"class":223,"line":305},[221,495,411],{"class":231},[221,497,271],{"class":227},[221,499,500,502],{"class":223,"line":313},[221,501,346],{"class":227},[221,503,421],{"class":235},[221,505,506],{"class":223,"line":324},[221,507,509],{"emptyLinePlaceholder":508},true,"\n",[221,511,512,514],{"class":223,"line":335},[221,513,277],{"class":231},[221,515,271],{"class":227},[221,517,518,520,522],{"class":223,"line":343},[221,519,285],{"class":231},[221,521,288],{"class":227},[221,523,291],{"class":235},[221,525,526,528,530],{"class":223,"line":352},[221,527,297],{"class":231},[221,529,288],{"class":227},[221,531,302],{"class":235},[221,533,534,536],{"class":223,"line":360},[221,535,308],{"class":231},[221,537,271],{"class":227},[221,539,540,542,544],{"class":223,"line":368},[221,541,316],{"class":231},[221,543,288],{"class":227},[221,545,321],{"class":235},[221,547,548,550,552],{"class":223,"line":376},[221,549,327],{"class":231},[221,551,288],{"class":227},[221,553,554],{"class":235},"\"80\u002Ftcp->web:80\"\n",[221,556,557,559],{"class":223,"line":388},[221,558,338],{"class":231},[221,560,271],{"class":227},[221,562,563,565],{"class":223,"line":398},[221,564,346],{"class":227},[221,566,349],{"class":235},[221,568,569,571],{"class":223,"line":408},[221,570,355],{"class":231},[221,572,271],{"class":227},[221,574,575,577],{"class":223,"line":416},[221,576,346],{"class":227},[221,578,365],{"class":235},[221,580,582,584],{"class":223,"line":581},18,[221,583,371],{"class":231},[221,585,271],{"class":227},[221,587,589,591,593],{"class":223,"line":588},19,[221,590,379],{"class":231},[221,592,288],{"class":227},[221,594,385],{"class":384},[221,596,598,600,602],{"class":223,"line":597},20,[221,599,391],{"class":231},[221,601,288],{"class":227},[221,603,385],{"class":384},[221,605,607,609,611],{"class":223,"line":606},21,[221,608,401],{"class":231},[221,610,288],{"class":227},[221,612,385],{"class":384},[221,614,616,618],{"class":223,"line":615},22,[221,617,411],{"class":231},[221,619,271],{"class":227},[221,621,623,625],{"class":223,"line":622},23,[221,624,346],{"class":227},[221,626,421],{"class":235},[221,628,630],{"class":223,"line":629},24,[221,631,509],{"emptyLinePlaceholder":508},[221,633,635,638],{"class":223,"line":634},25,[221,636,637],{"class":231},"networks",[221,639,271],{"class":227},[221,641,643,646],{"class":223,"line":642},26,[221,644,645],{"class":231},"  appnet",[221,647,271],{"class":227},[74,649,650],{},"Then start it:",[212,652,654],{"className":214,"code":653,"language":216,"meta":217,"style":217},"docker compose up -d\n",[83,655,656],{"__ignoreMap":217},[221,657,658,661,664,667],{"class":223,"line":224},[221,659,72],{"class":660},"sHkqI",[221,662,663],{"class":235}," compose",[221,665,666],{"class":235}," up",[221,668,669],{"class":384}," -d\n",[74,671,672,673,676],{},"Give it about 30 to 60 seconds and open ",[83,674,675],{},"http:\u002F\u002F{YOUR IP ADDRESS}"," from any network, or from\nyour phone on mobile data. The nginx welcome page means a request from the internet reached\nyour container.",[74,678,679,680,682,683,685,686,688,689,691,692,695],{},"Two things are worth noticing about that file. ",[83,681,431],{}," has no ",[83,684,85],{}," entry, so nginx is\nreachable from the internet while not being published on the machine running it. And both\nservices join ",[83,687,427],{},", because the agent resolves ",[83,690,431],{}," by service name and can only do that\non a network the two of them share. Compose would put both on the project's default network\nif you left the ",[83,693,694],{},"networks:"," blocks out entirely, and that works just as well, but naming the\nnetwork makes the requirement visible.",[91,697,699],{"id":698},"map-your-ports","Map your ports",[74,701,702,705,706,709],{},[83,703,704],{},"GETPUBLICIP_MAPPINGS"," decides which ports on your public IP go to which of your containers.\nSeparate entries with ",[83,707,708],{},";"," or newlines:",[212,711,716],{"className":712,"code":714,"language":715},[713],"language-text","publicPort[\u002Fproto[,proto]][\u002Fstack[,stack]]->service[:internalPort]\n","text",[83,717,714],{"__ignoreMap":217},[719,720,721,737],"table",{},[722,723,724],"thead",{},[725,726,727,731,734],"tr",{},[728,729,730],"th",{},"Part",[728,732,733],{},"Default",[728,735,736],{},"Notes",[738,739,740,762,782,799],"tbody",{},[725,741,742,748,753],{},[743,744,745],"td",{},[83,746,747],{},"proto",[743,749,750],{},[83,751,752],{},"tcp",[743,754,755,757,758,761],{},[83,756,752],{}," or ",[83,759,760],{},"udp","; comma-separate for both",[725,763,764,769,774],{},[743,765,766],{},[83,767,768],{},"stack",[743,770,771],{},[83,772,773],{},"ipv4",[743,775,776,757,778,781],{},[83,777,773],{},[83,779,780],{},"ipv6","; comma-separate for dual-stack",[725,783,784,789,792],{},[743,785,786],{},[83,787,788],{},"service",[743,790,791],{},"None",[743,793,794,795,798],{},"the destination, resolved at runtime: a compose ",[78,796,797],{},"service name",", any resolvable hostname, or a literal IPv4 address",[725,800,801,806,812],{},[743,802,803],{},[83,804,805],{},"internalPort",[743,807,808,809],{},"same as ",[83,810,811],{},"publicPort",[743,813,814],{},"the port on the destination",[719,816,817,827],{},[722,818,819],{},[725,820,821,824],{},[728,822,823],{},"Example",[728,825,826],{},"Meaning",[738,828,829,842,855,869],{},[725,830,831,836],{},[743,832,833],{},[83,834,835],{},"8080\u002Ftcp->web:80",[743,837,838,839,841],{},"TCP 8080 on your public IP goes to ",[83,840,431],{}," on port 80",[725,843,844,849],{},[743,845,846],{},[83,847,848],{},"443\u002Ftcp,udp->web:443",[743,850,851,852,854],{},"TCP and UDP 443 go to ",[83,853,431],{}," on port 443",[725,856,857,862],{},[743,858,859],{},[83,860,861],{},"53\u002Fudp->dns:53",[743,863,864,865,868],{},"UDP 53 goes to ",[83,866,867],{},"dns"," on port 53",[725,870,871,876],{},[743,872,873],{},[83,874,875],{},"9000\u002Ftcp\u002Fipv4,ipv6->api",[743,877,878],{},"dual-stack; the internal port defaults to 9000",[74,880,881,882,176,885,888],{},"The segments after the port are matched by keyword, so their order does not matter.\n",[83,883,884],{},"8080\u002Ftcp\u002Fipv4",[83,886,887],{},"8080\u002Fipv4\u002Ftcp"," mean the same thing.",[74,890,891,892,894],{},"Note that you refer to your containers by ",[78,893,797],{},", never by IP address. Compose\nassigns container IPs dynamically, so the agent resolves those names itself and re-checks\nthem every 30 seconds. When a container restarts on a different IP, the forwarding rules\nare updated automatically.",[74,896,897],{},"A destination can also be any hostname that resolves, or a literal IPv4 address, and all\nthree behave identically. A literal IPv6 address is not accepted; use a hostname with an AAAA\nrecord instead.",[91,899,901],{"id":900},"ssl-tls","SSL \u002F TLS",[74,903,904,905,908],{},"GetPublicIP works at the packet level. It forwards TCP and UDP to your containers and leaves\neverything above that to you, and encryption is one of the things above it. So if you serve\nHTTPS, ",[78,906,907],{},"your own service terminates the TLS connection",". Map 443 to the container that\nalready holds your certificate, and nothing about your TLS setup changes:",[212,910,912],{"className":259,"code":911,"language":261,"meta":217,"style":217},"GETPUBLICIP_MAPPINGS: \"443\u002Ftcp->web:443; 80\u002Ftcp->web:80\"\n",[83,913,914],{"__ignoreMap":217},[221,915,916,918,920],{"class":223,"line":224},[221,917,704],{"class":231},[221,919,288],{"class":227},[221,921,922],{"class":235},"\"443\u002Ftcp->web:443; 80\u002Ftcp->web:80\"\n",[446,924,926],{"id":925},"we-do-not-decrypt-your-traffic","We do not decrypt your traffic",[74,928,929],{},"Your public IP is not a TLS proxy. It holds none of your certificates or private keys, so it\nhas nothing to decrypt with. The TLS session is negotiated directly between your visitor's\nbrowser and your own service, and it stays encrypted the whole way: out of the browser, across\nthe internet to your public IP, through the WireGuard tunnel, and into your container, where it\nis decrypted for the first time.",[74,931,932],{},"The tunnel adds a second layer of encryption between our network and your machine, but it is\nthe outer layer. What travels inside it is the same TLS ciphertext your visitor sent. Your\ncertificates and private keys never leave your machine, there is nothing to upload to us, and\nyour traffic is never decrypted and re-encrypted on the way through. We do not decrypt TLS\ntraffic, and we have no plans to.",[446,934,936],{"id":935},"your-service-needs-a-valid-certificate","Your service needs a valid certificate",[74,938,939],{},"Because the certificate lives on whichever service terminates the connection, that service\nneeds a valid one, exactly as it would on any other public host.",[96,941,942,956,962,968],{},[99,943,944,947,948,951,952,955],{},[78,945,946],{},"Point a domain at your public IP."," Add an ",[83,949,950],{},"A"," record for your IPv4 address, and an ",[83,953,954],{},"AAAA","\nrecord if you serve IPv6 as well. Certificate authorities issue for domain names, so a bare\nIP address is not enough.",[99,957,958,961],{},[78,959,960],{},"Get the certificate from Let's Encrypt."," It is free, automated, and well suited to this.\nCaddy and Traefik request and renew for you with no more configuration than your domain\nname; nginx and Apache pair with certbot.",[99,963,964,967],{},[78,965,966],{},"If you use an HTTP-01 challenge, map port 80 as well as 443"," to the same service. Let's\nEncrypt connects to port 80 on your public IP to answer the challenge, and it fails if only\n443 is mapped. A DNS-01 challenge does not need port 80.",[99,969,970,973],{},[78,971,972],{},"Leave those ports mapped."," Renewal repeats the same challenge every 60 days or so, and it\nfails quietly if the mapping has gone.",[446,975,977],{"id":976},"plain-http-is-still-plain","Plain HTTP is still plain",[74,979,980],{},"A mapping to port 80 serving unencrypted HTTP is unencrypted between your visitor and your\npublic IP, exactly as it would be anywhere else on the internet. The WireGuard tunnel covers\nonly the hop between our network and your machine. If the content matters, terminate TLS.",[91,982,984],{"id":983},"forwarding-to-a-service-on-the-host","Forwarding to a service on the host",[74,986,987,988,991],{},"Already running something on the box and just want it public? Point a mapping at\n",[83,989,990],{},"host.docker.internal",", the address you would reach for anyway:",[212,993,995],{"className":259,"code":994,"language":261,"meta":217,"style":217},"services:\n  getpublicip:\n    environment:\n      GETPUBLICIP_MAPPINGS: \"8081\u002Ftcp->host.docker.internal:8081\"\n    extra_hosts:\n      - \"host.docker.internal:host-gateway\"   # needed on Linux; built in on Docker Desktop\n",[83,996,997,1003,1009,1015,1024,1031],{"__ignoreMap":217},[221,998,999,1001],{"class":223,"line":224},[221,1000,268],{"class":231},[221,1002,271],{"class":227},[221,1004,1005,1007],{"class":223,"line":274},[221,1006,277],{"class":231},[221,1008,271],{"class":227},[221,1010,1011,1013],{"class":223,"line":282},[221,1012,308],{"class":231},[221,1014,271],{"class":227},[221,1016,1017,1019,1021],{"class":223,"line":294},[221,1018,327],{"class":231},[221,1020,288],{"class":227},[221,1022,1023],{"class":235},"\"8081\u002Ftcp->host.docker.internal:8081\"\n",[221,1025,1026,1029],{"class":223,"line":305},[221,1027,1028],{"class":231},"    extra_hosts",[221,1030,271],{"class":227},[221,1032,1033,1035,1038],{"class":223,"line":313},[221,1034,346],{"class":227},[221,1036,1037],{"class":235},"\"host.docker.internal:host-gateway\"",[221,1039,1041],{"class":1040},"snpHw","   # needed on Linux; built in on Docker Desktop\n",[74,1043,1044],{},"Two things to check, because they produce the same \"it just doesn't work\" symptom:",[96,1046,1047,1063],{},[99,1048,1049,1059,1060,195],{},[78,1050,1051,1052,1055,1056,195],{},"The host service must listen on ",[83,1053,1054],{},"0.0.0.0",", not ",[83,1057,1058],{},"127.0.0.1"," A service bound to\nloopback refuses the connection from the container. Confirm with ",[83,1061,1062],{},"ss -lntp",[99,1064,1065,1068,1069,1072,1073,1076],{},[78,1066,1067],{},"A host firewall will drop it silently"," unless you allow the bridge subnet inbound on\n",[83,1070,1071],{},"docker0"," or the ",[83,1074,1075],{},"br-*"," interface. This is the one case on this page where a firewall rule\nis needed, and it covers the container-to-host hop, not inbound traffic from the internet.",[74,1078,1079,1080,195],{},"Host and container destinations can be mixed freely in a single ",[83,1081,704],{},[91,1083,1085],{"id":1084},"docker-compose-networks","Docker Compose networks",[74,1087,1088,1089,1092],{},"Because targets are resolved by service name, the ",[83,1090,1091],{},"getpublicip"," container has to be on the\nsame Docker network as the containers it forwards to.",[96,1094,1095,1101,1115],{},[99,1096,1097,1100],{},[78,1098,1099],{},"One default network."," Nothing to do, it works as-is.",[99,1102,1103,1106,1107,1109,1110,1114],{},[78,1104,1105],{},"Several networks."," List ",[83,1108,1091],{}," on ",[1111,1112,1113],"em",{},"every"," network that holds a service it\nforwards to.",[99,1116,1117,1120,1121,1139,1142,1143],{},[78,1118,1119],{},"Targets in a different compose project."," Both projects need to join a shared external\nnetwork:",[212,1122,1124],{"className":214,"code":1123,"language":216,"meta":217,"style":217},"docker network create shared\n",[83,1125,1126],{"__ignoreMap":217},[221,1127,1128,1130,1133,1136],{"class":223,"line":224},[221,1129,72],{"class":660},[221,1131,1132],{"class":235}," network",[221,1134,1135],{"class":235}," create",[221,1137,1138],{"class":235}," shared\n",[1140,1141],"br",{},"Then in each compose file:",[212,1144,1146],{"className":259,"code":1145,"language":261,"meta":217,"style":217},"services:\n  getpublicip:\n    networks:\n      - shared\n\nnetworks:\n  shared:\n    external: true\n",[83,1147,1148,1154,1160,1166,1173,1177,1183,1190],{"__ignoreMap":217},[221,1149,1150,1152],{"class":223,"line":224},[221,1151,268],{"class":231},[221,1153,271],{"class":227},[221,1155,1156,1158],{"class":223,"line":274},[221,1157,277],{"class":231},[221,1159,271],{"class":227},[221,1161,1162,1164],{"class":223,"line":282},[221,1163,411],{"class":231},[221,1165,271],{"class":227},[221,1167,1168,1170],{"class":223,"line":294},[221,1169,346],{"class":227},[221,1171,1172],{"class":235},"shared\n",[221,1174,1175],{"class":223,"line":305},[221,1176,509],{"emptyLinePlaceholder":508},[221,1178,1179,1181],{"class":223,"line":313},[221,1180,637],{"class":231},[221,1182,271],{"class":227},[221,1184,1185,1188],{"class":223,"line":324},[221,1186,1187],{"class":231},"  shared",[221,1189,271],{"class":227},[221,1191,1192,1195,1197],{"class":223,"line":335},[221,1193,1194],{"class":231},"    external",[221,1196,288],{"class":227},[221,1198,1199],{"class":384},"true\n",[74,1201,1202,1203,1206],{},"If this is wrong, the logs repeat ",[83,1204,1205],{},"waiting for destinations to resolve"," and the agent keeps\nretrying rather than failing outright.",[91,1208,1210],{"id":1209},"check-the-logs","Check the logs",[212,1212,1214],{"className":214,"code":1213,"language":216,"meta":217,"style":217},"docker compose up -d\ndocker compose logs -f getpublicip\n",[83,1215,1216,1226],{"__ignoreMap":217},[221,1217,1218,1220,1222,1224],{"class":223,"line":224},[221,1219,72],{"class":660},[221,1221,663],{"class":235},[221,1223,666],{"class":235},[221,1225,669],{"class":384},[221,1227,1228,1230,1232,1235,1238],{"class":223,"line":274},[221,1229,72],{"class":660},[221,1231,663],{"class":235},[221,1233,1234],{"class":235}," logs",[221,1236,1237],{"class":384}," -f",[221,1239,1240],{"class":235}," getpublicip\n",[74,1242,1243,1244,1247],{},"A healthy start brings the tunnel up on interface ",[83,1245,1246],{},"pi0",", resolves your destinations,\ninstalls the forwarding rules, and pushes the mappings to GetPublicIP. The agent then keeps\nrunning, re-checking the destinations and the tunnel itself on an interval.",[74,1249,1250],{},"This is what a healthy tunnel looks like in the log. It is worth knowing now, so you have\nsomething to compare against later:",[212,1252,1255],{"className":1253,"code":1254,"language":715},[713],"tunnel status up=true interfaces=1 peers=1 healthy=true handshakeAge=31s rxDelta=0\n",[83,1256,1254],{"__ignoreMap":217},[74,1258,1259,1262,1263,1266,1267,1270],{},[83,1260,1261],{},"healthy=true"," is the field that matters. Do not read ",[83,1264,1265],{},"up=true"," on its own as good news,\nbecause it only means the interface exists, and it stays ",[83,1268,1269],{},"true"," even on a tunnel that has\nquietly stopped passing traffic.",[91,1272,1274],{"id":1273},"testing","Testing",[74,1276,1277,1278,1281,1282,1285],{},"Give it about ",[78,1279,1280],{},"30 to 60 seconds"," before you test. Pushing new mappings puts your IP address into\nan ",[83,1283,1284],{},"updating"," state while the platform applies them, and requests made during that window\nfail. Two or three failures followed by success is the normal sequence, not a fault.",[74,1287,1288],{},"Test from a network that is not your own. Mobile data is the easiest option:",[212,1290,1292],{"className":214,"code":1291,"language":216,"meta":217,"style":217},"curl http:\u002F\u002F{YOUR IP ADDRESS}\n",[83,1293,1294],{"__ignoreMap":217},[221,1295,1296,1299,1302,1305],{"class":223,"line":224},[221,1297,1298],{"class":660},"curl",[221,1300,1301],{"class":235}," http:\u002F\u002F{YOUR",[221,1303,1304],{"class":235}," IP",[221,1306,1307],{"class":235}," ADDRESS}\n",[91,1309,444],{"id":1310},"connection-drops",[74,1312,1313],{},"Nothing to restart and nothing to configure. The tunnel re-establishes itself after a dropped\nconnection, a router reboot, or your ISP handing you a new address.",[74,1315,1316],{},"Most of the time it repairs itself without the agent having to do anything. The tunnel is an\noutbound connection held open by a keepalive every 25 seconds, and our server re-learns your\naddress from any packet the agent sends, so when your router drops the mapping or moves you\nto a new port, the next keepalive puts it back. On a real tunnel we measured traffic flowing\nagain about 17 seconds after connectivity returned.",[74,1318,1319,1320,1323,1324,1327],{},"For the rarer faults a keepalive cannot fix, the agent stops rather than trying to repair the\ntunnel in place, and Docker restarts it into a clean, fresh connection. In the log you will\nsee ",[83,1321,1322],{},"tunnel appears broken"," when it first notices, and then\n",[83,1325,1326],{},"exiting so the container is restarted"," if the problem persists across several checks. That is\nthe recovery working, not an error.",[74,1329,1330,1331,1333],{},"This is the one place the compose file has to be right: recovery depends on your restart\npolicy, so keep ",[83,1332,438],{}," on the service. Without it the container stops and\nstays stopped.",[74,1335,1336],{},"What this does not do is keep your service reachable while your internet is down. If the\nconnection to your machine is gone, so is your service. The tunnel simply picks itself back\nup as soon as there is a connection to use.",[91,1338,1340],{"id":1339},"container-permissions","Container permissions",[719,1342,1343,1353],{},[722,1344,1345],{},[725,1346,1347,1350],{},[728,1348,1349],{},"Setting",[728,1351,1352],{},"Why it is needed",[738,1354,1355,1365,1375,1385,1399],{},[725,1356,1357,1362],{},[743,1358,1359],{},[83,1360,1361],{},"cap_add: NET_ADMIN",[743,1363,1364],{},"Configure the WireGuard interface and the forwarding rules",[725,1366,1367,1372],{},[743,1368,1369],{},[83,1370,1371],{},"devices: \u002Fdev\u002Fnet\u002Ftun",[743,1373,1374],{},"The tunnel device itself",[725,1376,1377,1382],{},[743,1378,1379],{},[83,1380,1381],{},"net.ipv4.ip_forward=1",[743,1383,1384],{},"Forward traffic from the tunnel to your target container",[725,1386,1387,1392],{},[743,1388,1389],{},[83,1390,1391],{},"net.ipv4.conf.all.src_valid_mark=1",[743,1393,1394,1395,1398],{},"Required by ",[83,1396,1397],{},"wg-quick","'s routing setup",[725,1400,1401,1406],{},[743,1402,1403],{},[83,1404,1405],{},"net.ipv6.conf.all.forwarding=1",[743,1407,1408],{},"Only needed if you forward IPv6",[91,1410,175],{"id":1411},"macos",[74,1413,1414,1415,1418],{},"Use ",[107,1416,141],{"className":1417,"href":139,"target":140},[110],"\nand the compose file above works unchanged, on both Intel and Apple Silicon.",[74,1420,1421],{},"Other Docker runtimes for macOS (Colima, OrbStack, Rancher Desktop) run different kernels\nand have not been verified, so we cannot say whether the tunnel comes up on them.",[91,1423,17],{"id":1424},"windows",[74,1426,1427,1428,195],{},"The compose file above needs no changes on Windows using ",[107,1429,141],{"className":1430,"href":139,"target":140},[110],[74,1432,1433],{},"Docker Desktop needs 64-bit Windows 10 or 11, on a version still supported by Microsoft, and\nthe WSL 2 backend, which is the default. There are then two things to check before you start.",[74,1435,1436,1439],{},[78,1437,1438],{},"1. Docker Desktop must be in Linux containers mode."," The Windows containers engine cannot\nrun this image, and it is not available on the WSL 2 backend in any case.",[74,1441,1442,1445,1446,1449],{},[78,1443,1444],{},"2. Your WSL2 kernel must be 6.6.x."," Older kernels, 5.15 among them, are missing a\nnetfilter module the tunnel needs, and there is no way to work around it from inside the\ncontainer. The tunnel simply fails to come up with ",[83,1447,1448],{},"unknown option '--save-mark'",". Check and\nupdate from PowerShell:",[212,1451,1455],{"className":1452,"code":1453,"language":1454,"meta":217,"style":217},"language-powershell shiki shiki-themes monokai","wsl --version    # need \"Kernel version: 6.6.x\"\nwsl --update     # if it reports anything older\n","powershell",[83,1456,1457,1462],{"__ignoreMap":217},[221,1458,1459],{"class":223,"line":224},[221,1460,1461],{},"wsl --version    # need \"Kernel version: 6.6.x\"\n",[221,1463,1464],{"class":223,"line":274},[221,1465,1466],{},"wsl --update     # if it reports anything older\n",[74,1468,1469],{},"6.6 only became the settled default around WSL 2.4 to 2.7, so a machine that has not been\nupdated in a while can still be on 5.15. The same requirement applies if you run Docker CE\ninside a WSL2 distribution rather than Docker Desktop, because it shares the same kernel.",[74,1471,1472,1473,1476],{},"On 6.6, ",[83,1474,1475],{},"tun"," is a module rather than built in, so it is worth confirming the device node\nactually exists before blaming the agent. This command fails at container creation if it is\nmissing:",[212,1478,1480],{"className":1452,"code":1479,"language":1454,"meta":217,"style":217},"docker run --rm --device \u002Fdev\u002Fnet\u002Ftun alpine ls -l \u002Fdev\u002Fnet\u002Ftun\n",[83,1481,1482],{"__ignoreMap":217},[221,1483,1484],{"class":223,"line":224},[221,1485,1479],{},[74,1487,1488],{},"No Windows Firewall rule or port forward is needed. Inbound traffic arrives over the tunnel,\nso neither Windows Firewall nor WSL's NAT is in the path.",[91,1490,1492],{"id":1491},"laptops-and-sleep","Laptops and sleep",[74,1494,1495,1498],{},[78,1496,1497],{},"Sleep drops the tunnel."," On macOS and Windows the Linux VM suspends when your machine\ndoes, and your public IP is unreachable until it wakes, at which point the tunnel\nre-establishes itself automatically. Try it on your laptop by all means, but run it on a\nserver: a machine that sleeps is a poor host for an always-on public IP.",[91,1500,1502],{"id":1501},"one-api-key-one-tunnel","One API key, one tunnel",[1504,1505,1506],"blockquote",{},[74,1507,1508],{},"A WireGuard key belongs to a single endpoint. If two containers run with the same API key,\nthey will knock each other offline. Whichever one handshaked most recently wins, and the\nother silently drops traffic until its next keepalive. It looks like random flakiness\nrather than a clear error. The usual cause is testing on a laptop while a server is\nalready connected with the same key.",[74,1510,1511],{},"This one is self-diagnosing: both containers restart over and over, because each keeps\ndeciding it is the broken one. A container that is restart-looping on a machine whose internet\nis fine almost always means a second agent is using the same key.",[74,1513,1514],{},"Use one API key per running tunnel.",[91,1516,1518],{"id":1517},"troubleshooting","Troubleshooting",[74,1520,1521,1524],{},[83,1522,1523],{},"docker compose logs -f getpublicip"," is the place to start.",[719,1526,1527,1537],{},[722,1528,1529],{},[725,1530,1531,1534],{},[728,1532,1533],{},"What you see",[728,1535,1536],{},"What it means",[738,1538,1539,1552,1562,1571,1587,1603,1613,1621,1635,1647,1657],{},[725,1540,1541,1546],{},[743,1542,1543],{},[83,1544,1545],{},"GETPUBLICIP_API_KEY is required",[743,1547,1548,1549,1551],{},"The variable is not set, or your ",[83,1550,205],{}," file is not being picked up",[725,1553,1554,1559],{},[743,1555,1556],{},[83,1557,1558],{},"tunnel up: … 401",[743,1560,1561],{},"The API key is not valid",[725,1563,1564,1568],{},[743,1565,1566],{},[83,1567,1205],{},[743,1569,1570],{},"The target is not up yet, the service name is wrong, or the agent is not on a shared network with it",[725,1572,1573,1578],{},[743,1574,1575,1577],{},[83,1576,1397],{}," or permission errors",[743,1579,1580,757,1583,1586],{},[83,1581,1582],{},"NET_ADMIN",[83,1584,1585],{},"\u002Fdev\u002Fnet\u002Ftun"," is missing",[725,1588,1589,1594],{},[743,1590,1591],{},[83,1592,1593],{},"iptables-restore … unknown option '--save-mark'",[743,1595,1596,1597,1599,1600],{},"Windows only: a WSL2 kernel older than 6.6. Run ",[83,1598,166],{},". See ",[107,1601,17],{"className":1602,"href":180},[110],[725,1604,1605,1610],{},[743,1606,1607],{},[83,1608,1609],{},"error gathering device information … \u002Fdev\u002Fnet\u002Ftun",[743,1611,1612],{},"The device node does not exist on the host. On Docker Desktop, check your WSL2 kernel version",[725,1614,1615,1618],{},[743,1616,1617],{},"Traffic arrives but replies hang",[743,1619,1620],{},"The forwarding sysctls are not set",[725,1622,1623,1626],{},[743,1624,1625],{},"A host mapping is refused",[743,1627,1628,1629,1631,1632,1634],{},"The host service is bound to ",[83,1630,1058],{}," rather than ",[83,1633,1054],{},", or a host firewall is dropping the bridge subnet",[725,1636,1637,1641],{},[743,1638,1639],{},[83,1640,1322],{},[743,1642,1643,1644],{},"The connection dropped. The agent is already dealing with it. See ",[107,1645,444],{"className":1646,"href":443},[110],[725,1648,1649,1654],{},[743,1650,1651],{},[83,1652,1653],{},"tunnel health cannot be determined",[743,1655,1656],{},"The agent could not read the tunnel's status this time round, which is not the same as the tunnel being down. It is given longer to clear on its own before the container is restarted",[725,1658,1659,1663],{},[743,1660,1661],{},[83,1662,1326],{},[743,1664,1665],{},"Normal recovery, not an error. The container comes back with a fresh connection. If it repeats for a long time, either the machine has no internet or two agents are sharing one API key",[74,1667,1668],{},"To check a configuration without bringing anything up or contacting the API, run it in dry\nrun mode. It prints the rules it would apply and exits:",[212,1670,1672],{"className":214,"code":1671,"language":216,"meta":217,"style":217},"docker run --rm \\\n  -e GETPUBLICIP_API_KEY=x -e GETPUBLICIP_DRY_RUN=true \\\n  -e GETPUBLICIP_ISOLATED_INTERFACE=eth0 \\\n  -e GETPUBLICIP_MAPPINGS=\"8080\u002Ftcp->web:80\" \\\n  ghcr.io\u002Fgetpublicip\u002Fgetpublicip:latest\n",[83,1673,1674,1687,1705,1714,1723],{"__ignoreMap":217},[221,1675,1676,1678,1681,1684],{"class":223,"line":224},[221,1677,72],{"class":660},[221,1679,1680],{"class":235}," run",[221,1682,1683],{"class":384}," --rm",[221,1685,1686],{"class":384}," \\\n",[221,1688,1689,1692,1695,1698,1701,1703],{"class":223,"line":274},[221,1690,1691],{"class":384},"  -e",[221,1693,1694],{"class":235}," GETPUBLICIP_API_KEY=x",[221,1696,1697],{"class":384}," -e",[221,1699,1700],{"class":235}," GETPUBLICIP_DRY_RUN=",[221,1702,1269],{"class":384},[221,1704,1686],{"class":384},[221,1706,1707,1709,1712],{"class":223,"line":282},[221,1708,1691],{"class":384},[221,1710,1711],{"class":235}," GETPUBLICIP_ISOLATED_INTERFACE=eth0",[221,1713,1686],{"class":384},[221,1715,1716,1718,1721],{"class":223,"line":294},[221,1717,1691],{"class":384},[221,1719,1720],{"class":235}," GETPUBLICIP_MAPPINGS=\"8080\u002Ftcp->web:80\"",[221,1722,1686],{"class":384},[221,1724,1725],{"class":223,"line":305},[221,1726,1727],{"class":235},"  ghcr.io\u002Fgetpublicip\u002Fgetpublicip:latest\n",[91,1729,1731],{"id":1730},"frequently-asked-questions","Frequently Asked Questions",[1733,1734,1737,1741],"details",{"className":1735},[1736],"faq-item",[1738,1739,1740],"summary",{},"Do I need to publish ports on my containers to use GetPublicIP?",[74,1742,1743,1744,1746,1747,1749],{},"No. Traffic arrives through the WireGuard tunnel, not through a published host port, so the services you expose need no ",[83,1745,85],{}," entries and no changes at all. Adding ",[83,1748,85],{}," would additionally expose the service on your LAN, which is usually not what you want.",[1733,1751,1753,1756],{"className":1752},[1736],[1738,1754,1755],{},"Does the GetPublicIP container need to run as privileged?",[74,1757,1758,1759,1761,1762,1764],{},"No. The container needs the ",[83,1760,1582],{}," capability, the ",[83,1763,1585],{}," device, and the forwarding sysctls, and that is all.",[1733,1766,1768,1771],{"className":1767},[1736],[1738,1769,1770],{},"Do I need the WireGuard kernel module installed on the host?",[74,1772,1773,1774,1777],{},"No. The image uses the host's kernel module when it is available and otherwise falls back to the bundled userspace ",[83,1775,1776],{},"wireguard-go",". There is nothing to install on the host either way, on any platform.",[1733,1779,1781,1784],{"className":1780},[1736],[1738,1782,1783],{},"Does this work on macOS and Windows?",[74,1785,1786,1787,1789,1790,1792],{},"Yes, and the compose file needs no changes on either. Your container's host is a Linux VM rather than the desktop operating system, so the tunnel device comes from that VM and there is nothing to install on macOS or Windows itself. macOS works unmodified on Intel and Apple Silicon with Docker Desktop. On Windows, Docker Desktop must be in Linux containers mode and your WSL2 kernel must be 6.6.x. Check with ",[83,1788,162],{}," and run ",[83,1791,166],{}," if it reports anything older, because kernels before 6.6 are missing a netfilter module the tunnel requires.",[1733,1794,1796,1799],{"className":1795},[1736],[1738,1797,1798],{},"Does GetPublicIP decrypt my HTTPS traffic?",[74,1800,1801],{},"No. GetPublicIP forwards packets and never terminates TLS, so it holds none of your certificates or private keys and has nothing to decrypt with. The TLS session is negotiated directly between your visitor's browser and whichever of your own services terminates it, and it is decrypted for the first time when it gets there. That service needs a valid certificate of its own, exactly as it would on any other public host. Let's Encrypt is free and automated and suits this well, and if you use an HTTP-01 challenge remember to map port 80 alongside 443.",[1733,1803,1805,1808],{"className":1804},[1736],[1738,1806,1807],{},"Can I forward a port to a service running on the Docker host rather than a container?",[74,1809,1810,1811,1813,1814,1817,1818,1631,1820,1822],{},"Yes. Point the mapping at ",[83,1812,990],{},", adding ",[83,1815,1816],{},"extra_hosts: - \"host.docker.internal:host-gateway\""," on Linux. It is built in on Docker Desktop. A literal IP works too. The host service must also listen on ",[83,1819,1054],{},[83,1821,1058],{},", and a host firewall needs to allow the bridge subnet inbound.",[1733,1824,1826,1829],{"className":1825},[1736],[1738,1827,1828],{},"What happens if my internet connection drops?",[74,1830,1831,1832,1834],{},"The tunnel re-establishes itself, and there is nothing to restart and nothing to configure. Most drops are repaired by the keepalive that holds the connection open, without the agent having to intervene; on a real tunnel we measured traffic flowing again about 17 seconds after connectivity returned. For the rarer faults a keepalive cannot fix, the agent stops the container rather than trying to repair the tunnel in place, and your restart policy brings it straight back up with a fresh connection, which is why the compose snippet sets ",[83,1833,438],{},". While your internet is actually down your service is unreachable, and no tunnel changes that, but it picks itself back up as soon as there is a connection to use.",[1733,1836,1838,1841],{"className":1837},[1736],[1738,1839,1840],{},"What happens when one of my containers restarts with a new IP address?",[74,1842,1843,1844,1847],{},"The agent re-resolves every destination service name on an interval (",[83,1845,1846],{},"GETPUBLICIP_RESOLVE_INTERVAL",", 30 seconds by default) and re-applies the forwarding rules if a container has moved. You do not need to restart anything. A single dropped request right after a re-sync is expected while connection tracking settles.",[1733,1849,1851,1854],{"className":1850},[1736],[1738,1852,1853],{},"Can I run the same API key in two places at once?",[74,1855,1856],{},"No. A WireGuard key belongs to a single endpoint, so two containers using the same API key will knock each other offline. Whichever one handshakes most recently wins. It presents as random, intermittent failures rather than a clean error, though there is one clear signal: both containers restart over and over, because each keeps deciding it is the broken one. Use one API key per running tunnel.",[1858,1859],"hr",{},[74,1861,1862,1863,1866,1867,1870,1871,1874],{},"New to GetPublicIP? Start with ",[107,1864,7],{"className":1865,"href":5},[110],"\nfor how the service works. Running your services directly on a host instead of in containers?\nSee the ",[107,1868,12],{"className":1869,"href":10},[110]," and\n",[107,1872,17],{"className":1873,"href":15},[110]," guides.",[1876,1877,1878],"style",{},"html pre.shiki code .sCdxs, html code.shiki .sCdxs{--shiki-default:#F8F8F2}html pre.shiki code .s8I7P, html code.shiki .s8I7P{--shiki-default:#F92672}html pre.shiki code .s_Ekj, html code.shiki .s_Ekj{--shiki-default:#E6DB74}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html pre.shiki code .sHkqI, html code.shiki .sHkqI{--shiki-default:#A6E22E}html pre.shiki code .s7s5_, html code.shiki .s7s5_{--shiki-default:#AE81FF}html pre.shiki code .snpHw, html code.shiki .snpHw{--shiki-default:#88846F}",{"title":217,"searchDepth":274,"depth":274,"links":1880},[1881,1882,1883,1886,1887,1892,1893,1894,1895,1896,1897,1898,1899,1900,1901,1902,1903],{"id":93,"depth":274,"text":94},{"id":198,"depth":274,"text":199},{"id":252,"depth":274,"text":253,"children":1884},[1885],{"id":448,"depth":282,"text":449},{"id":698,"depth":274,"text":699},{"id":900,"depth":274,"text":901,"children":1888},[1889,1890,1891],{"id":925,"depth":282,"text":926},{"id":935,"depth":282,"text":936},{"id":976,"depth":282,"text":977},{"id":983,"depth":274,"text":984},{"id":1084,"depth":274,"text":1085},{"id":1209,"depth":274,"text":1210},{"id":1273,"depth":274,"text":1274},{"id":1310,"depth":274,"text":444},{"id":1339,"depth":274,"text":1340},{"id":1411,"depth":274,"text":175},{"id":1424,"depth":274,"text":17},{"id":1491,"depth":274,"text":1492},{"id":1501,"depth":274,"text":1502},{"id":1517,"depth":274,"text":1518},{"id":1730,"depth":274,"text":1731},"2026-08-09","Expose your Docker Compose services to the internet with a dedicated public IP. Add one container, map your ports, and your services stay exactly as they are.","md",[1908,1910,1912,1914,1916,1917,1919,1921,1923],{"question":1740,"answer":1909},"No. Traffic arrives through the WireGuard tunnel, not through a published host port, so the services you expose need no `ports:` entries and no changes at all. Adding `ports:` would additionally expose the service on your LAN, which is usually not what you want.",{"question":1755,"answer":1911},"No. The container needs the `NET_ADMIN` capability, the `\u002Fdev\u002Fnet\u002Ftun` device, and the forwarding sysctls, and that is all.",{"question":1770,"answer":1913},"No. The image uses the host's kernel module when it is available and otherwise falls back to the bundled userspace `wireguard-go`. There is nothing to install on the host either way, on any platform.",{"question":1783,"answer":1915},"Yes, and the compose file needs no changes on either. Your container's host is a Linux VM rather than the desktop operating system, so the tunnel device comes from that VM and there is nothing to install on macOS or Windows itself. macOS works unmodified on Intel and Apple Silicon with Docker Desktop. On Windows, Docker Desktop must be in Linux containers mode and your WSL2 kernel must be 6.6.x. Check with `wsl --version` and run `wsl --update` if it reports anything older, because kernels before 6.6 are missing a netfilter module the tunnel requires.",{"question":1798,"answer":1801},{"question":1807,"answer":1918},"Yes. Point the mapping at `host.docker.internal`, adding `extra_hosts: - \"host.docker.internal:host-gateway\"` on Linux. It is built in on Docker Desktop. A literal IP works too. The host service must also listen on `0.0.0.0` rather than `127.0.0.1`, and a host firewall needs to allow the bridge subnet inbound.",{"question":1828,"answer":1920},"The tunnel re-establishes itself, and there is nothing to restart and nothing to configure. Most drops are repaired by the keepalive that holds the connection open, without the agent having to intervene; on a real tunnel we measured traffic flowing again about 17 seconds after connectivity returned. For the rarer faults a keepalive cannot fix, the agent stops the container rather than trying to repair the tunnel in place, and your restart policy brings it straight back up with a fresh connection, which is why the compose snippet sets `restart: unless-stopped`. While your internet is actually down your service is unreachable, and no tunnel changes that, but it picks itself back up as soon as there is a connection to use.",{"question":1840,"answer":1922},"The agent re-resolves every destination service name on an interval (`GETPUBLICIP_RESOLVE_INTERVAL`, 30 seconds by default) and re-applies the forwarding rules if a container has moved. You do not need to restart anything. A single dropped request right after a re-sync is expected while connection tracking settles.",{"question":1853,"answer":1856},{},{"title":22,"icon":23,"description":24},{"title":65,"description":1905},"docs\u002F4.docker","2026-08-10","i_ke5D9T7VHQiNnDlp2_ibz0eRWUNVy-2t2Co6-ArYk",[1931,1933,1935,1937,1939],{"path":5,"navigation":1932},{"title":7,"icon":8},{"path":10,"navigation":1934},{"title":12,"icon":13},{"path":15,"navigation":1936},{"title":17,"icon":18},{"path":20,"navigation":1938},{"title":22,"icon":23,"description":24},{"path":26,"navigation":1940},{"title":28,"icon":29,"description":30},1786424706084]