TeslaLogger in the Cloud
For the last few months, I've been running TeslaLogger to log my car's journeys and charge statistics, with the aim of tracking battery health and charge performance over the long run. I've initially been running this on my Mac Mini at home, but thought I'd take advantage of Oracle's "Always Free Resources" and run it on a 4 core, 24GB RAM ARM64 instance in the cloud.
The goal here is to run Traefik 2 and TeslaLogger and it's Grafana instance on a Docker server, with Traefik 2 providing reverse proxying and SSL for TeslaLogger and Grafana without exposing the services to the internet directly - with Traefik 2 being the gatekeeper to them all. As a bonus, we'll add IPv6 support!
We'll assume you have Docker up and running on your machine. We'll also be using separate docker-compose files to keep TeslaLogger somewhat separate from Traefik, as you may be using Traefik for more services than just TeslaLogger.
Traefik 2 configuration
Files & Folders
Let's start with creating a folder in which you'll house your Traefik configuration. In my case that would be: mkdir -p ~/docker/traefik/
Next we'll create a folder for file based configuration of Traefik (we won't use this today but it's nice to have): mkdir -p ~/docker/traefik/dynamic_conf/
Lastly, we'll create a file for our certificate storage: touch ~/docker/traefik/acme.json
and change it's permissions: chmod 600 ~/docker/traefik/acme.json
Network
Next up, we create a docker network on which traefik and the services will be connected to, allowing them to communicate with eachother.
sudo docker network create traefik_default
docker-compose.yml
We'll move to the folder for Traefik: mv ~/docker/traefik/
Now, we need to create our service definition. The finaldocker-compose.yml
looks like this:
version: "2.1"
services:
ipv6:
image: robbertkl/ipv6nat
restart: unless-stopped
network_mode: "host"
privileged: true
volumes:
- /var/run/docker.sock:/var/run/docker.sock:ro
- /lib/modules:/lib/modules:ro
traefik:
image: "traefik:latest"
restart: unless-stopped
command:
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--providers.docker"
- "--providers.docker.exposedbydefault=false"
- "--api"
# Generic Cert Resolver
- "--certificatesresolvers.leresolver.acme.caserver=https://acme-v02.api.letsencrypt.org/directory"
- "--certificatesresolvers.leresolver.acme.email=your@email.here"
- "--certificatesresolvers.leresolver.acme.storage=/acme.json"
- "--certificatesresolvers.leresolver.acme.tlschallenge=true"
# Dynamic Configuration File
- "--providers.file.directory=/dynamic_conf"
# Stats
- "--accesslog=true"
ports:
- "80:80"
- "443:443"
networks:
- 'traefik_default'
- 'fd01'
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./acme.json:/acme.json"
- "./dynamic_conf:/dynamic_conf"
labels:
- "traefik.enable=true"
# Dashboard
- "traefik.http.routers.traefik.rule=Host(`traefik.foo.bar`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.tls=true"
- "traefik.http.routers.traefik.tls.certresolver=leresolver"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.middlewares=authtraefik"
- "traefik.http.middlewares.authtraefik.basicauth.users=florian:HTPASSWD" # user/password
# global redirect to https
- "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
networks:
fd01:
enable_ipv6: true
driver: bridge
driver_opts:
com.docker.network.enable_ipv6: "true"
ipam:
driver: default
config:
- subnet: fd01::/80
traefik_default:
external: true
Let's go over the diferent sections.
IPv6 & Networks
At the top, we're defining a service called ipv6
. This service will allow us to do NATing over IPv6, meaning that our Traefik instance will be accessible via IPv6. This is optional and you can just not have that service at all if you don't want IPv6.
In the networks, we create 2 networks. One called fd01
, the other traefik_default
. If you removed the IPv6 service at the top, you won't need fd01
here, so just remove that config. Be sure to also remove the fd01
network from the traefik service later on. The traefik_default
network will be the network that we use for traefik to communicate to our TeslaLogger service - and any other services you may want to have in the future.
Traefik Service
traefik:
image: "traefik:latest"
restart: unless-stopped
command:
- "--entrypoints.web.address=:80"
- "--entrypoints.websecure.address=:443"
- "--providers.docker"
- "--providers.docker.exposedbydefault=false"
- "--api"
# Generic Cert Resolver
- "--certificatesresolvers.leresolver.acme.caserver=https://acme-v02.api.letsencrypt.org/directory"
- "--certificatesresolvers.leresolver.acme.email=your@email.here"
- "--certificatesresolvers.leresolver.acme.storage=/acme.json"
- "--certificatesresolvers.leresolver.acme.tlschallenge=true"
# Dynamic Configuration File
- "--providers.file.directory=/dynamic_conf"
# Stats
- "--accesslog=true"
ports:
- "80:80"
- "443:443"
networks:
- 'traefik_default'
- 'fd01'
volumes:
- "/var/run/docker.sock:/var/run/docker.sock:ro"
- "./acme.json:/acme.json"
- "./dynamic_conf:/dynamic_conf"
labels:
- "traefik.enable=true"
# Dashboard
- "traefik.http.routers.traefik.rule=Host(`traefik.foo.bar`)"
- "traefik.http.routers.traefik.service=api@internal"
- "traefik.http.routers.traefik.tls=true"
- "traefik.http.routers.traefik.tls.certresolver=leresolver"
- "traefik.http.routers.traefik.entrypoints=websecure"
- "traefik.http.routers.traefik.middlewares=authtraefik"
- "traefik.http.middlewares.authtraefik.basicauth.users=florian:HTPASSWD" # user/password
# global redirect to https
- "traefik.http.routers.http-catchall.rule=hostregexp(`{host:.+}`)"
- "traefik.http.routers.http-catchall.entrypoints=web"
- "traefik.http.routers.http-catchall.middlewares=redirect-to-https"
- "traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https"
This is really our core section. We create 2 entrypoints, web
and websecure
on 80 and 443 respectively. We also make sure Docker containers are not exposed by default through Traefik, meaning they only get exposed if you add the corresponding labels: --providers.docker.exposedbydefault=false
Next we configure the certificate resolver, calling it leresolver
, just a generic one for LetsEncrypt. Be sure to configure your email address!
Lastly we enable the file configuration directory and enable the access log.
The ports and networks configuration is pretty straightforward as is the volume one. We created the files and folders that we're using for the volumes before, so be sure you use the right folder mapping here.
When it comes to labels
, we'll configure 2 things here:
- The Traefik dashboard
- Global HTTP->HTTPS redirect
First, we need to enable traefik for this service, as we don't expose services by default, using traefik.enable=true
.
For the dashboard, be sure to set your rule correctly, defining where you want your Traefik dashboard to be accessible: traefik.http.routers.traefik.rule=Host(`traefik.foo.bar`)
Please also configure your user and use htpasswd
to generate yourself a password for the basicauth middleware: traefik.http.middlewares.authtraefik.basicauth.users=florian:HTPASSWD
You could also not add this section if you don't want to expose the Traefik dashboard.
Lastly, we add a middleware to redirect all HTTP to HTTPS.
That's it for the traefik configuration.
You should now be able to bring up traefik using: docker-compose up -d
.
TeslaLogger configuration
Now that we have Traefik up and running, let's configure TeslaLogger. While we generally follow the instructions for the Docker setup, we use a slightly modified docker-compose.yml to allow for everything to run behind the reverse proxy.
We'll start with creating a folder for all our TeslaLogger stuff: mkdir -p ~/docker/teslalogger/
and then move into it: cd ~/docker/teslalogger/
Let's start with cloning the git repo into our folder: git clone https://github.com/bassmaster187/TeslaLogger
As per the global instructions, we'll copy over the config file and configure it: cp TeslaLogger/TeslaLogger/App.config TeslaLogger/TeslaLogger/bin/TeslaLogger.exe.config
And then we edit it: vi TeslaLogger/TeslaLogger/bin/TeslaLogger.exe.config
We configure the DBConnectionstring
to look something like: <value>Server=database;Database=teslalogger;Uid=root;Password=teslalogger;CharSet=utf8;</value>
Now, this is where we deviate from the official guide, as we'll use a slightly modified docker-compose file.
Our docker-compose.yml
version: "3"
services:
teslalogger:
build: TeslaLogger/docker/teslalogger/.
restart: always
volumes:
- ./TeslaLogger/TeslaLogger/www:/var/www/html
- ./TeslaLogger/TeslaLogger/bin:/etc/teslalogger
- ./TeslaLogger/TeslaLogger/GrafanaDashboards/:/var/lib/grafana/dashboards/
- ./TeslaLogger/TeslaLogger/GrafanaPlugins/:/var/lib/grafana/plugins
- ./TeslaLogger/docker/teslalogger/Dockerfile:/tmp/teslalogger-DOCKER
- teslalogger-tmp:/tmp/
depends_on:
- database
environment:
- TZ=Europe/Berlin
labels:
- "com.centurylinklabs.watchtower.enable=false"
networks:
- internal
- traefik_default
database:
image: mariadb:10.4.7
restart: always
env_file:
- ./TeslaLogger/.env
volumes:
- ./TeslaLogger/TeslaLogger/sqlschema.sql:/docker-entrypoint-initdb.d/sqlschema.sql
- ./TeslaLogger/TeslaLogger/mysql:/var/lib/mysql
environment:
- TZ=Europe/Berlin
networks:
- internal
grafana:
image: grafana/grafana:8.3.2
restart: always
environment:
- GF_PLUGINS_ALLOW_LOADING_UNSIGNED_PLUGINS=natel-discrete-panel,pr0ps-trackmap-panel,teslalogger-timeline-panel
- TZ=Europe/Berlin
- GF_SERVER_ROOT_URL=http://tesla.foo.bar:3000/grafana/
- GF_SERVER_SERVE_FROM_SUB_PATH=true
volumes:
- ./TeslaLogger/TeslaLogger/bin:/etc/teslalogger
- ./TeslaLogger/TeslaLogger/GrafanaDashboards/:/var/lib/grafana/dashboards/
- ./TeslaLogger/TeslaLogger/GrafanaPlugins/:/var/lib/grafana/plugins
- ./TeslaLogger/TeslaLogger/GrafanaConfig/datasource.yaml:/etc/grafana/provisioning/datasources/datasource.yml
- ./TeslaLogger/TeslaLogger/GrafanaConfig/sample.yaml:/etc/grafana/provisioning/dashboards/dashboards.yml
depends_on:
- database
labels:
- "traefik.enable=true"
- "traefik.http.routers.tl_grafana.rule=Host(`tesla.foo.bar`) && PathPrefix(`/grafana`)"
- "traefik.http.routers.tl_grafana.entrypoints=websecure"
- "traefik.http.routers.tl_grafana.tls=true"
- "traefik.http.routers.tl_grafana.tls.certresolver=leresolver"
- "traefik.http.services.tl_grafana.loadbalancer.server.port=3000"
- "traefik.docker.network=traefik_default"
networks:
- traefik_default
- internal
webserver:
build: TeslaLogger/docker/webserver/.
restart: always
volumes:
- ./TeslaLogger/docker/webserver/php.ini:/usr/local/etc/php/php.ini
- ./TeslaLogger/TeslaLogger/www:/var/www/html
- ./TeslaLogger/TeslaLogger/bin:/etc/teslalogger
- ./TeslaLogger/docker/teslalogger/Dockerfile:/tmp/teslalogger-DOCKER
- ./TeslaLogger/TeslaLogger/GrafanaConfig/datasource.yaml:/tmp/datasource-DOCKER
- teslalogger-tmp:/tmp/
labels:
- "com.centurylinklabs.watchtower.enable=false"
- "traefik.enable=true"
- "traefik.http.routers.teslalogger.rule=Host(`tesla.foo.bar`) && PathPrefix(`/admin`)"
- "traefik.http.routers.teslalogger.entrypoints=websecure"
- "traefik.http.routers.teslalogger.tls=true"
- "traefik.http.routers.teslalogger.tls.certresolver=leresolver"
- "traefik.http.services.teslalogger.loadbalancer.server.port=80"
- "traefik.docker.network=traefik_default"
networks:
- traefik_default
- internal
environment:
- TZ=Europe/Berlin
volumes:
teslalogger-tmp:
networks:
traefik_default:
external: true
internal:
internal: true
teslalogger serivce
We make sure we build the code from the TeslaLogger
directory, not our current one. We're also mapping the volumes to that subdirectory.
We'll also connect the instance to our traefik_default
and internal
network. That's it for this service.
database service
For the database, we'll again change the mount folders, and then connect it only to our internal
network.
grafana service
This is the service where we'll need to make some changes. We're adding a few environment variables, the key ones being:
- GF_SERVER_ROOT_URL=http://tesla.foo.bar:3000/grafana/
- GF_SERVER_SERVE_FROM_SUB_PATH=true
We'll also correct the folder mapping in the volumes section as per the other ones and then we add some labels for traefik to pick up the instance:
labels:
- "traefik.enable=true"
- "traefik.http.routers.tl_grafana.rule=Host(`tesla.foo.bar`) && PathPrefix(`/grafana`)"
- "traefik.http.routers.tl_grafana.entrypoints=websecure"
- "traefik.http.routers.tl_grafana.tls=true"
- "traefik.http.routers.tl_grafana.tls.certresolver=leresolver"
- "traefik.http.services.tl_grafana.loadbalancer.server.port=3000"
- "traefik.docker.network=traefik_default"
Be sure to configure your rule
correctly to match your domain name you want to serve it on.
Lastly, we'll connect grafana to both the internal
and traefik_default
network
webserver service
Again, we'll adjust the folder mappings here, and then we need to add the labels for traefik.
labels:
- "com.centurylinklabs.watchtower.enable=false"
- "traefik.enable=true"
- "traefik.http.routers.teslalogger.rule=Host(`tesla.foo.bar`) && PathPrefix(`/admin`)"
- "traefik.http.routers.teslalogger.entrypoints=websecure"
- "traefik.http.routers.teslalogger.tls=true"
- "traefik.http.routers.teslalogger.tls.certresolver=leresolver"
- "traefik.http.services.teslalogger.loadbalancer.server.port=80"
- "traefik.docker.network=traefik_default"
Lastly, same as the grafana
service, we connect it to both traefik_default
and internal
under networks.
Launching TeslaLogger
Now that we have configured it all, let's simply launch it with docker-compose up -d
You should be able to access your TeslaLogger instance by visiting https://tesla.foo.bar/admin/
- of course using your configured domain.
Grafana will be available under https://tesla.foo.bar/grafana/
.
Please make sure you configure a TeslaLogger password, as otherwise your instance will be accessible by anyone and change your Grafana user password.
Summary
That's it. You're now running TeslaLogger securely in the cloud, behind Traefik without the need for any VPN.
You can find a follow up post on how to run TeslaETA alongside this over here.