{"id":668,"date":"2025-10-01T20:04:51","date_gmt":"2025-10-01T19:04:51","guid":{"rendered":"https:\/\/www.flat-planet.net\/?p=668"},"modified":"2025-10-01T20:04:54","modified_gmt":"2025-10-01T19:04:54","slug":"self-hosted-nightscout-xdrip","status":"publish","type":"post","link":"https:\/\/flat-planet.net\/?p=668","title":{"rendered":"Self-Hosted Nightscout &amp; xDrip"},"content":{"rendered":"\n<p class=\"wp-block-paragraph\">I&#8217;ve recently had multiple issues with the official <a href=\"http:\/\/www.dexcom.com\/en-ie\/dexcom-g7-cgm-system\">Dexcom G7<\/a> app on <a href=\"https:\/\/www.dexcom.com\/compatibility\/g7\">my phone<\/a>, and after they <a href=\"https:\/\/www.reddit.com\/r\/dexcom\/comments\/1nrbl22\/build_your_own_g7_app_wont_run\/\">broke the BYOD version of the app,<\/a> I decided to move in another direction.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">xDrip<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">As of <a href=\"https:\/\/github.com\/NightscoutFoundation\/xDrip\/releases\/tag\/2025.08.08\">2025.08.08<\/a>, xDrip now supports the G7 directly. Just two notes here. Follow the <a href=\"https:\/\/navid200.github.io\/xDrip\/docs\/Dexcom\/G7.html\">instructions<\/a> to the letter, and ignore the screenshots &#8211; the options to select are Dex, and use the 4 digit bluetooth pairing code as the Transmitter ID.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large is-resized is-style-default\"><a href=\"https:\/\/www.flat-planet.net\/wp-content\/uploads\/2025\/10\/Screenshot_2025-10-01-16-40-26-18_2dd2c1bcb8f659cd696bcba2072f751b-1.jpg\"><img loading=\"lazy\" decoding=\"async\" width=\"465\" height=\"1024\" src=\"https:\/\/www.flat-planet.net\/wp-content\/uploads\/2025\/10\/Screenshot_2025-10-01-16-40-26-18_2dd2c1bcb8f659cd696bcba2072f751b-1-465x1024.jpg\" alt=\"\" class=\"wp-image-670\" style=\"width:293px;height:auto\" srcset=\"https:\/\/flat-planet.net\/wp-content\/uploads\/2025\/10\/Screenshot_2025-10-01-16-40-26-18_2dd2c1bcb8f659cd696bcba2072f751b-1-465x1024.jpg 465w, https:\/\/flat-planet.net\/wp-content\/uploads\/2025\/10\/Screenshot_2025-10-01-16-40-26-18_2dd2c1bcb8f659cd696bcba2072f751b-1-136x300.jpg 136w, https:\/\/flat-planet.net\/wp-content\/uploads\/2025\/10\/Screenshot_2025-10-01-16-40-26-18_2dd2c1bcb8f659cd696bcba2072f751b-1-768x1690.jpg 768w, https:\/\/flat-planet.net\/wp-content\/uploads\/2025\/10\/Screenshot_2025-10-01-16-40-26-18_2dd2c1bcb8f659cd696bcba2072f751b-1-698x1536.jpg 698w, https:\/\/flat-planet.net\/wp-content\/uploads\/2025\/10\/Screenshot_2025-10-01-16-40-26-18_2dd2c1bcb8f659cd696bcba2072f751b-1-931x2048.jpg 931w, https:\/\/flat-planet.net\/wp-content\/uploads\/2025\/10\/Screenshot_2025-10-01-16-40-26-18_2dd2c1bcb8f659cd696bcba2072f751b-1-624x1373.jpg 624w, https:\/\/flat-planet.net\/wp-content\/uploads\/2025\/10\/Screenshot_2025-10-01-16-40-26-18_2dd2c1bcb8f659cd696bcba2072f751b-1.jpg 1080w\" sizes=\"auto, (max-width: 465px) 100vw, 465px\" \/><\/a><\/figure>\n\n\n\n<p class=\"wp-block-paragraph\">With xDrip working well, I then decided to get <a href=\"http:\/\/www.nightscout.info\/\">Nightscout<\/a> up and running to act as a replacement for the <a href=\"https:\/\/clarity.dexcom.com\/\">Clarity<\/a> reports.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Nightscout<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\"><a href=\"http:\/\/www.nightscout.info\/\">Nightscout<\/a> depends on MongoDB as the backend storage, so my docker-compose.yml file starts a <a href=\"https:\/\/github.com\/colinmacgiolla\/nightscout-on-docker\/blob\/53606c94a1fc2f76767c711677c098658d4b1da3\/docker-compose.yml#L104\">nightscout-mongodb<\/a> service. The main change here from other examples is that the bitnami containers are now under <a href=\"https:\/\/github.com\/colinmacgiolla\/nightscout-on-docker\/blob\/53606c94a1fc2f76767c711677c098658d4b1da3\/docker-compose.yml#L105C12-L105C19\">bitnamilegacy<\/a>, since its no longer being maintained &#8211; not a huge concern since its completely internal to the nightscout docker network.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  nightscout-mongodb:                                   # SERVICE_NAME - used by the \"depends_on:\" to make sure the database it running before things try to use it\n    image: bitnamilegacy\/mongodb:latest                       # Other options \"image: mongo:latest\" will need adjustments in other places such as \"volumes:\" mounts\n    container_name: nightscout-mongodb                  # called in Nightscout's MONGODB_URI. General good practice to be the same as SERVICE_NAME but not required\n    environment:\n      - PUID=1000                                               # Not necessary, but sets the USER who runs the container as not root\n      - PGID=1000                                               # Not necessary, but sets the USER who runs the container as not root\n      - TZ=Europe\/Dublin                                       # Refer to {nightscout-app}\n      - MONGO_INITDB_ROOT_USERNAME=mongodbpoweruser             # May be necessry if importing from another database or doing management on the database\n      - MONGO_INITDB_DATABASE=auth                              # May be optional, hard to find documaentation about it\n      - MONGO_INITDB_ROOT_PASSWORD=mongoRootPassword03          # Will probably never be needed, I set it the same as MONGODB_ROOT_PASSWORD\n      - MONGODB_ROOT_USER=mongodbpoweruser                      # I'm not sure what the differences are but is usually set the same as MONGO_INITDB_ROOT_USERNAME\n      - MONGODB_ROOT_PASSWORD=mongoaRootPassword02              # I'm not sure what the differences are but is usually set the same as MONGO_INITDB_ROOT_PASSWORD\n      - MONGODB_USERNAME=nightscout01                           # Username called in Nightscout's MONGODB_URI - I set to just \"Nightscout\"\n      - MONGODB_PASSWORD=mongoPassword01                        # Password called in Nightscout's MONGODB_URI\n      - MONGODB_DATABASE=nightscout02                           # Database called in Nightscout's MONGODB_URI - I set to just \"Nightscout\"\n      - MONGODB_PORT_NUMBER=27017                               # DEFAULT&#91;27017] - Port each MongoDB will use inernally 27017:&lt;&lt;MONGODB_PORT_NUMBER>>\n    #ports:                                                  # Only needed to access database externally. If all parts are in the same stack\/network removal or # suggested\n    #  - 27017:27017                                             # DEFAULT&#91;27017:27017] - &lt;&lt;EXTERNAL PORT>>:&lt;&lt;MONGODB_PORT_NUMBER>>\n    volumes:\n      - .\/database:\/bitnami\/mongodb:rw        # \/LOCATION\/ON\/YOUR\/SYSTEM:\/CONTAINER\/LOCATION - for \"mongo:latest\" use \":\/data\/db\"\n      - .\/log:\/var\/log:rw                         # Refer to {nightscout-app}\n      - \/etc\/localtime:\/etc\/localtime:ro                        # Refer to {nightscout-app}\n    restart: always                                             # Refer to {nightscout-app}\n    networks:\n      - nightscout\n    healthcheck:                                                # OPTIONAL: This adds a Healthcheck to the container. Instead of just saying `&#91;Running]` it will say `&#91;Healthy]` if it is unhealthy it should restart\n      test: &#91;\"CMD\", \"sh\", \"-c\", \"mongosh --eval 'db.runCommand(\\\"ping\\\")' || exit 1\", \"||\", \"bash\", \"-c\", \"kill -s 15 1 &amp;&amp; sleep 10 &amp;&amp; kill -s 9 1\"]\n      interval: 10s\n      timeout: 10s\n      retries: 3\n      start_period: 20s<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Nightscout itself is can be completely configured from the <a href=\"https:\/\/github.com\/colinmacgiolla\/nightscout-on-docker\/blob\/53606c94a1fc2f76767c711677c098658d4b1da3\/docker-compose.yml#L25\">docker-compose<\/a> file, but the most important parameters are below. Be sure to update (and keep secret) the API_SECRET. I&#8217;ve set it to use unsecured\/HTTP, as I&#8217;ll terminate the HTTPS using trafik (more on this later).<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>      - TZ=Europe\/Dublin                                        # Use your Time Zone from https:\/\/wikipedia.org\/wiki\/List_of_tz_database_time_zones\n      - API_SECRET=myAPIsecret123                               # In xDrip: REST-API > Base URL= \"&lt;&lt;API_SECRET>>@&lt;&lt;BASE_URL>>\/api\/v1\/\"\n      - MONGODB_URI=mongodb:\/\/nightscout01:mongoPassword01@nightscout-mongodb:27017\/nightscout02\n      - INSECURE_USE_HTTP=true                                  # DEFAULT&#91;false] - Set this to \"true\", unless you have your own SSL provided internally, if you wish to access without reverse Proxy\n      - BASE_URL=https:\/\/nightscout.my.domain               #URL of your Nightscout site accessed outside the local network\n      - DISPLAY_UNITS=mmol\/L                                    # DEFAULT&#91;mg\/dl] - CHOICES: \"=mg\/dl\" | \"=mmol\" \"=mmol\/L\" (same thing either works)\n      - AUTH_DEFAULT_ROLES=denied                               # DEFAULT&#91;readable] - CHOICES: \"readable\" | \"denied\" | &lt;&lt;Or any valid role name>>\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">That is enough to get the local service up, running and reachable, but I also wanted it to be accessible via the interwebs. I could just tailscale it, but since I have a public IP and self-host other services, I decided to open it up.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Traefik<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">I&#8217;m using Trafeik to do two things &#8211; (1) SSL Termination, and (2) Rate-Limiting connections to the service.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">SSL Termination<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Its worth taking a look at the Traefik configuration in detail, as I found most of the online examples a bit obscure, or not quite covering my use case, which is that I have wildcard certs for my domain already, and wanted to use those.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>  traefik:\n    image: traefik:latest\n    container_name: traefik\n    restart: always\n    command:\n      - --providers.docker.network=nightscout\n      - --entryPoints.https1337.address=:1337\n      - --providers.docker=true\n      - --providers.file.directory=\/etc\/traefik\/dynamic\n      - --providers.file.watch=true\n    ports:\n      - \"1337:1337\"\n    volumes:\n      - \/var\/run\/docker.sock:\/var\/run\/docker.sock:ro\n      - .\/traefik\/dynamic\/tls.yml:\/etc\/traefik\/dynamic\/tls.yml\n      - .\/traefik\/certs:\/certs           # Your wildcard certs here\n    labels:\n      - \"traefik.enable=true\"\n    networks:\n      - nightscout<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">So to begin with we are creating a service called traefik, using the latest image, and allowing it to alwas restart. Then in the commands we are doing the following;<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Setting the provider network to be our defined <code>nightscout<\/code> network<\/li>\n\n\n\n<li>Creating a https entry point on port 1337 (default nightscout port)<\/li>\n\n\n\n<li>Setting docker as a provider of config<\/li>\n\n\n\n<li>Setting it watching the directory \/etc\/traefik\/dynamic for what is known as &#8220;dynamic config&#8221; &#8211; this contains a tls,yml file were we actually point it at our certificate and key<\/li>\n<\/ul>\n\n\n\n<p class=\"wp-block-paragraph\">Then we are exposing port 1337 to the outside world, mounting the volumes &#8211; specifically the tls.yml file, and the certs directory where our certificate and key are located. The rest is just the required label, and the mapping to the <code>nightscout<\/code> network that the whole service is running on.<br><br>The tls.yml file is fairly straightforward &#8211; any issues with this file or the certs themselves and traefik will default to using a self-signed cert.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>tls:\n  stores:\n    default:\n      defaultCertificate:\n        certFile: \/certs\/fullchain.cer\n        keyFile: \/certs\/certificate.key\n<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Finally we need to label our nightscout service to put traefik in front of it, as follows;<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>    labels:\n      - \"traefik.enable=true\"\n      - \"traefik.http.routers.netscout.rule=Host(`nightscout.my.domain`)\"\n      - \"traefik.http.routers.netscout.entrypoints=https1337\"\n      - \"traefik.http.routers.netscout.tls=true\"\n      - \"traefik.http.services.netscout.loadbalancer.server.port=1337\"<\/code><\/pre>\n\n\n\n<p class=\"wp-block-paragraph\">Main points are to create a http router called nightscout, enable TLS, give the domain, and map the entrypoint defined earlier. This is all you need to provide HTTPS connectivity on port 1337, terminated by traefik, and then forwarded to the nightscout-app service.<\/p>\n\n\n\n<h3 class=\"wp-block-heading\">Rate Limiting<\/h3>\n\n\n\n<p class=\"wp-block-paragraph\">Given that I&#8217;m planning on exposing this port to the bad bad internet, it makes sense to rate limit it. Thankfully this is just a few extra lines in the <a href=\"https:\/\/github.com\/colinmacgiolla\/nightscout-on-docker\/blob\/53606c94a1fc2f76767c711677c098658d4b1da3\/docker-compose.yml#L93\">labels<\/a> assigned to the nightscout-app service.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>      - \"traefik.http.middlewares.netscout-ratelimit.ratelimit.average=10\"\n      - \"traefik.http.middlewares.netscout-ratelimit.ratelimit.burst=5\"\n      - \"traefik.http.routers.netscout.middlewares=netscout-ratelimit@docker\"<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">CloudFlare<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Interestingly, even after opening up the ports on my router, I was unable to connect. After trying a few things, it eventually came to mind that possibly CloudFlare was doing something funny, since it sits between my public domain name, and my dynamic domain. <\/p>\n\n\n\n<p class=\"wp-block-paragraph\">It turns out that CloudFlare will proxy\/inspect all HTTP\/HTTPS traffic (orange-clouded) but only for a limited set of ports, (443, 2053, 2083, 2087,2096,8443) unless you have CloudFlare Spectrum or setup Cloudflare Tunnel \/ Argo Tunnel to create a secure tunnel between your origin server and the CloudFlare proxy.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"> Alternatively, if you want to serve HTTPS on a non-standard port but do not want to use Spectrum or Tunnel, you must set the DNS record (subdomain) to &#8220;grey cloud&#8221; (DNS only), which disables the proxy, so Cloudflare does not interfere with that traffic.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">TODO<\/h2>\n\n\n\n<p class=\"wp-block-paragraph\">Try moving the external port to one of those listed above and see if I can keep CF between me and the rest of the world.<\/p>\n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"<p>I&#8217;ve recently had multiple issues with the official Dexcom G7 app on my phone, and after they broke the BYOD version of the app, I decided to move in another direction. xDrip As of 2025.08.08, xDrip now supports the G7 directly. Just two notes here. Follow the instructions to the letter, and ignore the screenshots [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[438,83,38,95,27],"tags":[444,439,443,446,42,441,33,442,445,440],"series":[],"class_list":["post-668","post","type-post","status-publish","format-standard","hentry","category-diabetes","category-internet","category-linux","category-data-networking","category-projects","tag-cloudflare","tag-diabetes","tag-docker","tag-docker-compose","tag-linux-2","tag-nightscout","tag-projects-2","tag-self-hosting","tag-traefik","tag-xdrip"],"_links":{"self":[{"href":"https:\/\/flat-planet.net\/index.php?rest_route=\/wp\/v2\/posts\/668","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/flat-planet.net\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/flat-planet.net\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/flat-planet.net\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/flat-planet.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=668"}],"version-history":[{"count":3,"href":"https:\/\/flat-planet.net\/index.php?rest_route=\/wp\/v2\/posts\/668\/revisions"}],"predecessor-version":[{"id":673,"href":"https:\/\/flat-planet.net\/index.php?rest_route=\/wp\/v2\/posts\/668\/revisions\/673"}],"wp:attachment":[{"href":"https:\/\/flat-planet.net\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=668"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/flat-planet.net\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=668"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/flat-planet.net\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=668"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/flat-planet.net\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=668"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}