This article describes some learning efforts to deploy a service in a container enviroment, The service is deployed right now at

Repo for this article: Service URL:

This work has been adopted from

The service

Echoip is a service that will echo back your public IP address. Original repo is hosted at and as soon as I found out it was an opensource project that I could host myself I took the task to implement it in one of my VPS servers running the docker daemon.

Furthermore, the original author has implemented capabilities to read GeoIP databases and return more information on the visiting IP, such as Country, City, ASN and reverse PTR check, see the section below that describes how to gat this data.

Building the docker image

Since I wanted to host my own container image, I opted to rebuild the container and host it in my Docker Hub account. After authenticating with docker hub with docker login, I ran the following from the root directory using the exisitng Dockerfile:

docker build -t beszan/echoip:v1.0 .                  # Builds the image
docker tag beszan/echoip:v1.0 beszan/echoip:latest    # tags with latest so it is easy to use
docker push beszan/echoip                             # push versions in docker hub

The image is now hosted in my Docker Hub account. You can do the same with your docker username.

Testing the container

Since this is a very experimental service for me, I used one of the VPS hosts that I currently have installed docker daemon. This host by the way is also hosting a software version of RIPE Atlas probe that you can see here.

After reviewing the repository I was able to quickly run the service as a docker detached container.

$  docker run --rm --detach \
> --log-driver json-file --log-opt max-size=10m \
> --cpus=1 --memory=64m --memory-reservation=64m \
> --name echoip --hostname "$(hostname --fqdn)" \
>   -p 8080:8080 \
> beszan/echoip -H X-Real-IP

Unable to find image 'beszan/echoip:latest' locally
latest: Pulling from beszan/echoip
676ee85450f5: Pull complete 
28d7554a8df6: Pull complete 
Digest: sha256:5d113ef52bf90290758681fdb2790ff65645f20cf37ee3532440a7e66681baa2
Status: Downloaded newer image for beszan/echoip:latest

$  docker ps
CONTAINER ID   IMAGE           COMMAND                  CREATED         STATUS         PORTS                                       NAMES
238bba267a61   beszan/echoip   "/opt/echoip/echoip …"   5 seconds ago   Up 2 seconds>8080/tcp, :::8080->8080/tcp   echoip

$  curl localhost:8080

This allowed me to quickly test the image capabilties and make sure the container could run, but since the service would only listen to port 8080, it is not very practical for accessing it. I needed a way to expose the service to a more reachable port such as 80 and 443, a dedicated hostname and have the capability to implement a SSL cert in front of the service.

Implementing echoip as a docker-compose service

Since I don’t have a k8s cluster in production yet, I opted to run the service through a docker-compose file. This enabled me to place a frontend to the echoip container and use a more friendly domain name.

I chose to use for the service hostname so I added a DNS A record pointing to the VPS server where docker is running. As a frontend reverse proxy for the container I used Traefik since it was very easy to configure and setup.

Initial tests were started from the quickstart and then I was able to deep dive in their documentation website. We are clearly using the docker provider to allow Traefik to discover docker services based on labels and the Let’s Encrypt free SSL for our service.

We will be listening to both HTTP and HTTPS ports since requests might be coming from both web browsers and CLI clients such as curl.

After some back and forth with the configs, in the end this is the final docker-compose.yml file that worked for me:

version: "3.3"
    image: "traefik:v2.8"
    container_name: "traefik"
    restart: always
      - "--providers.docker=true"
      - "--providers.docker.exposedbydefault=false"
      - "--entrypoints.web.address=:80"
      - "--entrypoints.websecure.address=:443"
      - "--entryPoints.web.forwardedHeaders.trustedIPs=,,"
      - "--entryPoints.websecure.forwardedHeaders.trustedIPs=,,"
      - "--certificatesresolvers.myresolver.acme.httpchallenge=true"
      - "--certificatesresolvers.myresolver.acme.httpchallenge.entrypoint=web"
      - ""
      - ""
      - "80:80"
      - "443:443"
      - "/var/run/docker.sock:/var/run/docker.sock:ro"
      - "./letsencrypt:/letsencrypt"

    image: beszan/echoip:v1.5
    container_name: echoip
    restart: always
    hostname: ""
    command: ["-r", "-H", "X-Real-IP", "-a", "/usr/share/GeoIP/GeoLite2-ASN.mmdb", "-c", "/usr/share/GeoIP/GeoLite2-City.mmdb", "-f", "/usr/share/GeoIP/GeoLite2-Country.mmdb"]
      - "/usr/share/GeoIP/:/usr/share/GeoIP/"
          memory: 64M
      - "traefik.enable=true"
      - "traefik.http.routers.echoip-https.rule=Host(``)"
      - "traefik.http.routers.echoip-https.entrypoints=websecure"
      - "traefik.http.routers.echoip-https.tls=true"
      - "traefik.http.routers.echoip-https.service=echoip-https"
      - "traefik.http.routers.echoip-https.tls.certresolver=myresolver"
      - ""
      - ""
      - "traefik.http.routers.echoip-http.rule=Host(``)"
      - "traefik.http.routers.echoip-http.entrypoints=web"
      - ""
      - "traefik.http.routers.echoip-http.service=echoip-http"
      - ""

To start the service you can just run once docker-compose up -d. Since I am using restart=always for the containers, this service will start automatically in case the underlaying OS restarts.

[root@vps4 echoip]# docker-compose up -d
WARNING: Some services (echoip) use the 'deploy' key, which will be ignored. Compose does not support 'deploy' configuration - use `docker stack deploy` to deploy to a swarm.
Starting traefik ... 
Starting traefik ... done

Checking containers

[root@vps4 echoip]# docker ps | grep -E 'echo|traefik|CONTA'
CONTAINER ID   IMAGE                        COMMAND                  CREATED         STATUS              PORTS                                                                      NAMES
8683b586bd15   traefik:v2.8                 "/ --pr…"   8 hours ago     Up About a minute>80/tcp, :::80->80/tcp,>443/tcp, :::443->443/tcp   traefik
59eb6310a309   beszan/echoip:v1.5           "/opt/echoip/echoip …"   8 hours ago     Up About a minute   8080/tcp                                                                   echoip
[root@vps4 echoip]#

Explaining the docker-compose Traefik options

I am skipping the docker-compose options/configs as it is out of the scope of this article and focusing more on the reverse proxy config. The Traefik daemon can be configured with either ENV variables, a static configuration yaml or toml file or with command arguments. In my case we are using command arguments as it is the easiest way to spin the container service.

# Tells Traefik to use docker service discovery. This is achieved through the volume mount at `/var/run/docker.sock:/var/run/docker.sock:ro`
# This parameter does not allow any other docker service exposed unless told to do so
# listen on port 80 HTTP
# listen on port 443 HTTPS
# forward received headers on port 80 to localhost and the docker containers only
# forward received headers on port 443 to localhost and the docker containers only

Let’s Encrypt section

I am using the HTTP01 challenge for domain verification.

# Use the HTTP01 ACME challenge
# allow port 80 for Let's encrypt to do file verification
# Let's encrypt needs a valid email account
# Where to save the challenge information and the generated certificate

Echoip service labels section

These labels are under the echoip service in the docker-compose.yml file

# allow service to be discovered by Traefik

# Listen to this hostname on HTTPS port. Notice the backticks for the hostname variable.
# Then when matching that hostname let Traefik know this is a TLS service and use the "myresolver"
# certresolver for ACME Let's Encrypt automatic certificate generation.

# This section is the same but for port 80 HTTP. Traefik needs separate services for each published ports/services"

Adding GeoIP data

If you notice the echoip service in the docker-compose.yml file, it contains the command section which has some parameters to load GeoIP databases for countries, cities and ASNs so it can then display on the client side. I wrote a BASH script that will pull these databases located in scripts/ to pull the neccessary databases. You have to run this script first before running the docker-compose command.

The script pulls the database files from this repo: Script content below.

# Database directory

# Files to download

# If http proxy needed

# DB directory
test -w $DBDIR && cd $DBDIR 2>/dev/null || { echo "Invalid directory: $DBDIR"; exit 1; }

# Sleep 0-600 sec if started from cron
if [ ! -t 0 ]; then sleep $((RANDOM/54)); fi

export https_proxy
for f in $FILES; do
  wget -nv -N -T 30 $f --directory-prefix=$DBDIR
  if [ $RET -ne 0 ]; then
    echo "wget $f failed: $RET" >&2

Using the service

The service can be used from any web client, be it a gui or a cli. Example with Chrome:

Example with CLI:

$ curl

Example with json output

$  curl
  "ip": "",
  "ip_decimal": 1088941566,
  "country": "Canada",
  "country_iso": "CA",
  "country_eu": false,
  "region_name": "Ontario",
  "region_code": "ON",
  "zip_code": "L6S",
  "city": "Brampton",
  "latitude": 43.7387,
  "longitude": -79.7271,
  "time_zone": "America/Toronto",
  "asn": "AS577",
  "asn_org": "BACOM",
  "hostname": "",
  "user_agent": {
    "product": "curl",
    "version": "7.68.0",
    "raw_value": "curl/7.68.0"