{"id":19760972,"url":"https://github.com/sreehariskumar/dockerizing-your-ip-geolocation-service-with-nginx-reverse-proxy","last_synced_at":"2025-02-28T02:49:26.938Z","repository":{"id":69972257,"uuid":"604486804","full_name":"sreehariskumar/Dockerizing-Your-IP-Geolocation-Service-with-Nginx-Reverse-Proxy","owner":"sreehariskumar","description":"Dockerizing a flask application running an ip-geolocation service, a redis container used for caching and an nginx container configured as a reverse proxy","archived":false,"fork":false,"pushed_at":"2023-03-07T06:10:33.000Z","size":18,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-01-10T23:27:00.905Z","etag":null,"topics":["aws-secrets-manager","docker","flask-application","ipgeolocation-api","nginx-reverse-proxy"],"latest_commit_sha":null,"homepage":"","language":"Dockerfile","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/sreehariskumar.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-02-21T06:51:01.000Z","updated_at":"2023-02-21T06:58:01.000Z","dependencies_parsed_at":null,"dependency_job_id":"9c5a5cbd-adb2-46be-a1f0-231c88633b65","html_url":"https://github.com/sreehariskumar/Dockerizing-Your-IP-Geolocation-Service-with-Nginx-Reverse-Proxy","commit_stats":null,"previous_names":[],"tags_count":0,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sreehariskumar%2FDockerizing-Your-IP-Geolocation-Service-with-Nginx-Reverse-Proxy","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sreehariskumar%2FDockerizing-Your-IP-Geolocation-Service-with-Nginx-Reverse-Proxy/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sreehariskumar%2FDockerizing-Your-IP-Geolocation-Service-with-Nginx-Reverse-Proxy/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/sreehariskumar%2FDockerizing-Your-IP-Geolocation-Service-with-Nginx-Reverse-Proxy/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/sreehariskumar","download_url":"https://codeload.github.com/sreehariskumar/Dockerizing-Your-IP-Geolocation-Service-with-Nginx-Reverse-Proxy/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":241090709,"owners_count":19908003,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["aws-secrets-manager","docker","flask-application","ipgeolocation-api","nginx-reverse-proxy"],"created_at":"2024-11-12T03:39:13.580Z","updated_at":"2025-02-28T02:49:26.932Z","avatar_url":"https://github.com/sreehariskumar.png","language":"Dockerfile","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Dockerizing Your IP Geolocation Service with Nginx Reverse Proxy\n\n[![Build Status](https://travis-ci.org/joemccann/dillinger.svg?branch=master)](https://travis-ci.org/joemccann/dillinger)\n\n![geo-api drawio](https://user-images.githubusercontent.com/68052722/223335486-bba98176-ccd7-46fc-a1ca-4599b5650fd1.png)\n\n\n\n### Features\nIn this tutorial, we will be looking at dockerizing a flask application running an ip-geolocation service, a redis container used for caching and an nginx container configured as a reverse proxy.\n\n**Click [here](https://github.com/sreehariskumar/Launching-a-High-Availability-IP-Geolocation-Application-using-Docker-Swarm.md) to view a high availability model of the same application built using docker swarm.**\n\n| Docker | Version |\n| ------ | ------ |\n| Client | 20.10.17 |\n| Server | 20.10.17 |\n\n### Requirements:\n- Docker must be [installed](https://www.docker.com/) and docker daemon running.\n- The user running the docker commands may be added to the docker group or else you may need sudo privilege to run the commands. (optional)\n- Create an account in [IP Geolocation API](https://ipgeolocation.io/) \u0026 copy the API key.\n- A self-signed certificate. (Any source can be used)\n- A valid domain name.\n\n### Working\n- We will be creating a custom bridge to enable name-based communication between containers. All the containers being created will be attached to this bridge.\n- The requests are received by an nginx container which is configured as a reverse proxy server which will forward the requests to the configured backend containers.\n- We have 3 API containers running a flask application coded to contact the API service provided to fetch and server the details of a provided IP address.\n- A data caching service is provided by a redis container.\nThe API containers first examine the redis container to see if there is any cached information for an IP address when we first request its details. Details are served directly from the redis container if they are present.\n- If not, the container makes contact with the API service provider, gathers the data, and then sends it to the redis container to be served and cached.\n\n### AWS Secrets Manager\nFollow this [document to create a secret](https://docs.aws.amazon.com/secretsmanager/latest/userguide/create_secret.html) to store your API key. We will be configuring the API container to fetch the key from the secrets manager.\n\n**You will need a project directory to save all your files.\n**\n\n```s\nmkdir -p ip-geo/{conf,ssl} ; cd ip-geo/\n```\nThe **conf** sub directory is to keep the nginx conf file and the **ssl** sub directory is to keep the self-signed certificates.\n\n### Let’s look at the code of the flask application.\nThe files can be cloned from this git repository.\n```s\ngit clone https://github.com/sreehariskumar/ip-geo-location-finder.git\n```\n\n**Explanation:**\nThe code sets up a Flask web application that provides the geolocation information of an IP address through an API endpoint. The API calls an external service [ipgeolocation.io](https://ipgeolocation.io/), to retrieve the geolocation information. The API is designed to cache the response from ipgeolocation.io in a Redis database for a specified amount of time (1 hour) to reduce the number of API calls and improve response time.\n\nWhen the API receives a request for IP geolocation information, it first checks the Redis database for a cached response. If the response is in the cache, the API returns the cached response. If the response is not in the cache, the API calls ipgeolocation.io to retrieve the geolocation information and stores the response in the Redis database with an expiration time of 1 hour. The API then returns the response from ipgeolocation.io.\n\nThe **API key** for accessing ipgeolocation.io can be specified as an environment variable or retrieved from [AWS Secrets Manager](https://docs.aws.amazon.com/secretsmanager/latest/userguide/intro.html).\n\nThe code uses the following libraries:\n- **os**: provides a way to interact with the underlying operating system, such as reading environment variables.\n- **requests**: makes HTTP requests to external APIs.\n- **json**: deals with JSON data format.\n- **redis**: provides a Python client for interacting with Redis databases.\n- **flask**: provides a framework for building and running a web application.\n- **boto3**: provides a Python client for interacting with AWS services.\n\nThe application can fetch the API key either as plain text from the command line or from the AWS Secrets Manager service. It is ideal to store your sensitive data in any secret manager to ensure security.\n\n```s\ncat requirements.txt\n\nrequests\nredis\nflask\nboto3\n```\n\n**Explanation:**\nThe required packages are listed in the “requirements.txt” file. These are installed using pip for Python 3.\n\n```s\ncat Dockerfile\n\nFROM alpine:3.17\nENV FLASK_PORT 8080\nENV FLASK_DOCUMENTROOT /var/flaskapp\nENV FLASK_USER flaskuser\nRUN mkdir -p $FLASK_DOCUMENTROOT    \nRUN adduser -h $FLASK_DOCUMENTROOT  -D  -s /bin/sh  $FLASK_USER    \nWORKDIR $FLASK_DOCUMENTROOT\nCOPY .  .\nRUN chown -R  $FLASK_USER:$FLASK_USER $FLASK_DOCUMENTROOT\nRUN  apk update \u0026\u0026  apk add python3 py3-pip --no-cache\nRUN pip3 install -r requirements.txt \nEXPOSE $FLASK_PORT\nUSER $FLASK_USER\nCMD [\"app.py\"]\nENTRYPOINT [\"python3\"]\n```\n\n**Explanation:**\nThis Dockerfile has several environment variables to configure the Flask app:\n- **FROM alpine:3.17** — specifies the base image used for the build, which is Alpine Linux version 3.17.\n- **FLASK_PORT**: sets the port that the Flask app will listen on. This is set to 8080.\n- **FLASK_DOCUMENTROOT**: sets the root directory where the Flask app will be stored. This is set to /var/flaskapp.\n- **FLASK_USER**: sets the user that the Flask app will run as. This is set to “flaskuser”.\nThe application is configured under the privilege of this user which eliminates the risk of root privilege for the process running.\n- **mkdir -p $FLASK_DOCUMENTROOT**: creates the root directory for the Flask app.\n- **adduser -h $FLASK_DOCUMENTROOT -D -s /bin/sh $FLASK_USER**: creates the user for the Flask app.\n- **WORKDIR $FLASK_DOCUMENTROOT**: sets the current working directory to the root directory of the Flask app.\n- **COPY . .**: copies all the files in the current directory to the current working directory within the Docker image.\n- **chown -R $FLASK_USER:$FLASK_USER $FLASK_DOCUMENTROOT**: sets the owner and group of the Flask app files to the \"flaskuser\".\n- **apk update \u0026\u0026 apk add python3 py3-pip --no-cache**: updates the Alpine package index and installs Python 3 and pip for Python 3.\n- **pip3 install -r requirements.txt**: installs the packages listed in the \"requirements.txt\" file using pip for Python 3.\n\nThe Dockerfile then uses the **EXPOSE** command to specify the port that the Flask app will listen on. In this case, the port is specified using the **FLASK_PORT** environment variable.\n\nThe **USER** instruction sets the default user to run the app as. In this case, the user is specified using the **FLASK_USER** environment variable. It matters where you put this instruction because any instructions you issue after defining the USER instruction will only be able to be executed by this user, not as root.\n\nFinally, **CMD** and **ENTRYPOINT** commands specify the default command to run the app. The **CMD** command specifies \"app.py\" as the file to run, and the **ENTRYPOINT** command specifies \"python3\" as the interpreter to run the file with.\n\nIn summary, this Dockerfile sets up an Alpine Linux-based Docker image to run a Flask app, specifying its configuration, dependencies, and default command to run.\n\n**Let’s run a temporary nginx container to get the nginx configuration file.\n**\n```s\ndocker container run --name nginx-temp -d --rm nginx:alpine\n```\n\nThe [docker run](https://docs.docker.com/engine/reference/commandline/run/) command will pull an nginx:alpine image first if not present locally, and then runs a container.\nAs this is a temporary container, the **-rm** lag is defined to remove the container when it exits.\n\n```s\ndocker container exec nginx-temp ls -l /etc/nginx/conf.d/\n\ntotal 4\n-rw-r--r--    1 root     root          1093 Feb  2 05:36 default.conf\n```\n\nThe [docker exec](https://docs.docker.com/engine/reference/commandline/exec/) is used to run a command in a running container. We run **ls -l** command to list the configuration file.\nWe will be choosing the **default.conf** file located at **/etc/nginx/conf.d/** location of the container.\n\n```s\ndocker container cp nginx-temp:/etc/nginx/conf.d/default.conf ./conf/.\n```\n\nThe [docker cp](https://docs.docker.com/engine/reference/commandline/cp/) command is used to copy files/folders between a container and the local filesystem.\nHere, we copy the configuration file from the container to our project directory in our local system.\n\n### What is the difference between nginx.conf file and default.conf file?\nThe **nginx.conf** file is the main configuration file for Nginx and is typically located at **/etc/nginx/nginx.conf**. This file contains the global settings for Nginx and is used to configure how Nginx behaves overall.\n\nThe **default.conf** file, on the other hand, is a server block configuration file that is usually stored in the **/etc/nginx/conf.d/** directory. This file is used to specify the behaviour of a particular virtual host or server block, and it contains settings such as the server name, listen to addresses and ports, and reverse proxy configurations.\n\n```s\ndocker container stop nginx-temp\n```\n\nWe can stop the container with the [docker stop](https://docs.docker.com/engine/reference/commandline/stop/) command. The container will be removed automatically as we’ve mention --rm flag during creation.\n\n### Let’s begin by creating a custom bridge.\n\n```s\ndocker network create geo-net\n\ndocker network ls \n```\n\n### Let’s run the redis container first.\n\n```s\ndocker container run -d --name geo-redis --restart always --network geo-net redis:latest\n```\n\nThe **geo-redis** container will be constructed with the restart policy set to **always**, which ensures that it will always start back up after being stopped. Additionally, the **geo-net** custom bridge network will be attached to the container.\n\n```s\ndocker container ls -a\nCONTAINER ID   IMAGE          COMMAND                  CREATED          STATUS          PORTS      NAMES\n115cb58ed643   redis:latest   \"docker-entrypoint.s…\"   30 seconds ago   Up 29 seconds   6379/tcp   geo-redis\n```\n\n### Attaching Role to an EC2 instance.\nYou will need to create and attach a role with a **custom policy** to have read privilege over your stored secret.\n\n```s\n{\n    \"Version\": \"2012-10-17\",\n    \"Statement\": [\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": [\n                \"secretsmanager:GetResourcePolicy\",\n                \"secretsmanager:GetSecretValue\",\n                \"secretsmanager:DescribeSecret\",\n                \"secretsmanager:ListSecretVersionIds\"\n            ],\n            \"Resource\": \"arn:aws:secretsmanager:xxxxxxxxxx\"\n        },\n        {\n            \"Effect\": \"Allow\",\n            \"Action\": \"secretsmanager:ListSecrets\",\n            \"Resource\": \"*\"\n        }\n    ]\n}\n```\n\nAlthough this policy can list all secrets, it only has read access to your secret.\nFollow the [instructions](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/iam-roles-for-amazon-ec2.html#attach-iam-role) mentioned in the document on attaching a role to an EC2 instance.\n\n### Now, it is time to spin up the API containers.\n```s\ndocker container run -d --name geo-api-1 --restart always -e REDIS_PORT=\"6379\" -e REDIS_HOST=\"geo-redis\" -e APP_PORT=\"8080\" -e API_KEY_FROM_SECRETSMANAGER=\"True\" -e SECRET_NAME=\"xxxxx\" -e SECRET_KEY=\"xxxxx\" -e REGION_NAME=\"xxxxx\" --network geo-net sreehariskumar/ip-geo-location-finder:latest\n```\n\n**Explanation:**\n- **docker container run** is the command to run a new container.\n- **-d** runs the container in the background as a daemon.\n- **--name geo-api-1** gives the container the name \"geo-api-1\".\n- **--restart always** specifies that the container should always restart, even if it exits or crashes.\n- **-e REDIS_PORT=\"6379\"** and **-e REDIS_HOST=\"geo-redis\"** set environment variables in the container. In this case, **REDIS_PORT** is set to 6379, and **REDIS_HOST** is set to \"geo-redis\".\n- **-e APP_PORT=\"8080\"** sets the application to run on port 8080.\n- **-e API_KEY_FROM_SECRETSMANAGER=\"True\"** sets the application to fetch the API key from the AWS Secrets Manager.\n- **-e SECRET_NAME=\"xxxxx\"** and **-e SECRET_KEY=\"xxxxx\"** defines the secret name and the secret key to fetch out the secret.\n- **-e REGION_NAME=\"xxxxx\"** defines the region of our stored secret.\n- **--network geo-net** attaches the container to a Docker network named \"geo-net\".\n- **sreehariskumar/ip-geo-location-finder:latest** is the image name and tag used to run the container.\n- Similarly, let’s create two more of these containers under different names.\n\n```s\ndocker container run -d --name geo-api-2 --restart always -e REDIS_PORT=\"6379\" -e REDIS_HOST=\"geo-redis\" -e APP_PORT=\"8080\" -e API_KEY_FROM_SECRETSMANAGER=\"True\" -e SECRET_NAME=\"xxxxx\" -e SECRET_KEY=\"xxxxx\" -e REGION_NAME=\"xxxxx\" --network geo-net sreehariskumar/ip-geo-location-finder:latest\n```\n```s\ndocker container run -d --name geo-api-3 --restart always -e REDIS_PORT=\"6379\" -e REDIS_HOST=\"geo-redis\" -e APP_PORT=\"8080\" -e API_KEY_FROM_SECRETSMANAGER=\"True\" -e SECRET_NAME=\"xxxxx\" -e SECRET_KEY=\"xxxxx\" -e REGION_NAME=\"xxxxx\" --network geo-net sreehariskumar/ip-geo-location-finder:latest\n```\n\n### Self-Signed Certificate\nI'm using [this](https://getacert.com/selfsignedcert.html) website for the creating a self-signed certificate. You can choose any website of your choice.\n\nOnce you'ce provided the details, you can download the certificate and key file:\n\n```s\nwget https://getacert.com/ca/certs/wild.1by2.online-2023-02-02-071920.cer -O ./ssl/wild.1by2.online.cer\n```\n```s\nwget https://getacert.com/ca/certs/wild.1by2.online-2023-02-02-071920.pkey -O wild.1by2.online.key\n```\n\n### Now, let’s modify the nginx as a reverse proxy.\n\n```s\ncat default.conf\n\nserver { \n  listen 80;\n  listen [::]:80;\n  server_name geolocation-api.1by2.online;\n  return 301 https://$host$request_uri;\n}\n\nserver {\n  listen 443 ssl;\n  listen [::]:443 ssl;\n  ssl_sertificate /etc/nginx/ssl/wild.1by2.online.cer;\n  ssl_certificate_key /etc/nginx/ssl/wild.1by2.online.key;\n  server_name geolocation-api.1by2.online;\n  \n  location / {\n    proxy_set_header Host $host;\n    proxy_set_header X-Real-IP $remote_addr;\n    proxy_set_header X-Forwareded-For $proxy_add_x_forwarded_for;\n    proxy_set_header X-Forwarded-Proto $scheme;\n    proxy_pass http://geo-api;\n  }\n}\n\nupstream geo-api {\n  server geo-api-1:8080;\n  server geo-api-2:8080;\n  server geo-api-3:8080;\n}\n```\n\n**Explanation:**\n- This is how we configure the Nginx container to serve HTTP requests.\n- The first server block **listens** on ports 80 and 443 for both IPv4 and IPv6 addresses and is used to redirect HTTP requests to HTTPS by returning a 301 redirect status code to the client.\n- The second server block **listens** on ports 443 (using SSL) and serves HTTPS requests. It also sets up an SSL certificate and key files for encryption and specifies the server name.\n- Within the second server block, the **location** block sets up a reverse proxy to forward incoming requests to an upstream server group **geo-api**(any name can be used), which consists of three servers **geo-api-1**, **geo-api-2**, and **geo-api-3**, running on port 8080. The **proxy_set_header** directives set header values in the proxied request.\n- An **upstream** group is used to define a set of backend servers that will receive requests from Nginx. Nginx will forward incoming requests to one of the servers in this upstream group based on the load-balancing algorithm specified in the Nginx configuration.\n\n### Advantages of using Nginx over Application Load Balancer\n- **Flexibility**: Nginx provides a wide range of configuration options and can be used for various purposes beyond load balancing, such as caching, request redirection, and SSL termination.\n- **Performance**: Nginx has a smaller memory footprint and can handle a large number of connections, making it suitable for high-traffic websites.\n- **Cost**: Nginx is open-source software and free to use, whereas the Application Load Balancer service is a paid service offered by AWS.\n- **Control**: By using Nginx, you have full control over the configuration and behaviour of the reverse proxy, which can be important for certain use cases where customization is required.\n- **Portability**: Nginx can be deployed on various platforms and operating systems, making it easy to move between different environments.\n\n_It is important to note that both Nginx and Application Load Balancer have their own strengths and weaknesses, and the choice between the two ultimately depends on the specific requirements of your application and infrastructure._\n\n### Launching the Nginx container\n```s\ndocker container run -d --name proxy -v $(pwd)/conf/default.conf:/etc/nginx/conf.d/default.conf -v $(pwd)/ssl:/etc/nginx/ssl -p 80:80 -p 443:443  --network geo-net nginx:alpine\n```\n```s\ndocker container ls -a\n\nCONTAINER ID   IMAGE                                          COMMAND                  CREATED             STATUS              PORTS                                                                      NAMES\n552692ca74fd   nginx:alpine                                   \"/docker-entrypoint.…\"   17 minutes ago      Up 17 minutes   0.0.0.0:80-\u003e80/tcp, :::80-\u003e80/tcp, 0.0.0.0:443-\u003e443/tcp, :::443-\u003e443/tcp   proxy\n24f839351127   sreehariskumar/ip-geo-location-finder:latest   \"python3 app.py\"         19 minutes ago      Up 19 minutes   8080/tcp                                                                   geo-api-3\n03f33d6da5be   sreehariskumar/ip-geo-location-finder:latest   \"python3 app.py\"         19 minutes ago      Up 19 minutes   8080/tcp                                                                   geo-api-2\n971877efa1f8   sreehariskumar/ip-geo-location-finder:latest   \"python3 app.py\"         19 minutes ago      Up 19 minutes   8080/tcp                                                                   geo-api-1\n115cb58ed643   redis:latest                                   \"docker-entrypoint.s…\"   25 minutes ago      Up 25 minutes   6379/tcp                                                                   geo-redis\n```\n\n**Explanation:**\n- **-d**: Run the container in the background.\n- **--name proxy**: Name the container \"proxy\".\n- **-v $(pwd)/conf/default.conf:/etc/nginx/conf.d/default.conf**: Mount the local file \"default.conf\" in the \"conf\" directory to the \"/etc/nginx/conf.d/default.conf\" file in the container.\n- **-v $(pwd)/ssl:/etc/nginx/ssl**: Mount the local \"ssl\" directory to the \"/etc/nginx/ssl\" directory in the container.\n- **-p 80:80**: Map the host's port 80 to the container's port 80.\n- **-p 443:443**: Map the host's port 443 to the container's port 443.\n**(Both ports are published to handle HTTP \u0026 HTTPS requests)**\n- **--network geo-net**: Attach the container to the \"geo-net\" network to enable name-based communication between the containers.\n\n_In short, this command creates a Docker container running Nginx, maps the host’s ports 80 and 443 to the container’s ports, and mounts the local “conf” and “ssl” directories to the container’s “/etc/nginx” directory._\n\n### Adding record in Route53\nCreate a record pointing to the ip address of the instance to access the application publicly.\nI've given **geolocation-api.1by2.online**\nFollow the [document](https://docs.aws.amazon.com/Route53/latest/DeveloperGuide/resource-record-sets-creating.html) for creating a record in Route53.\n\n### Finally, try accessing the application.\n=\u003e _http://geolocation-api.1by2.online/ip/8.8.8.8_\n\nYou can **ignore** the warning as we’re using self-signed certificate here.\n\n### Result\nAs you could see, the HTTP request has been redirected to HTTPS. Also, you could see the details of the IP address that we’ve provided. You could see that the highlighted portion mentions the value **“cached”: “False”**.\nThe **\"apiServer”** value displays the ID of the container serving the data. Please note the **“apiServer”** value here.\n\nIf you refresh the page, you could see **“cached”: “True”**, which means the value has been cached this time.\nAlso, the **“apiServer”** value is different this time. (The request has been redirected to another container this time by the nginx container.)\n\n\n\n**I hope you were able to follow. Hope that it might help you guys.\nPlease share your reviews.**\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsreehariskumar%2Fdockerizing-your-ip-geolocation-service-with-nginx-reverse-proxy","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsreehariskumar%2Fdockerizing-your-ip-geolocation-service-with-nginx-reverse-proxy","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsreehariskumar%2Fdockerizing-your-ip-geolocation-service-with-nginx-reverse-proxy/lists"}