How can I make a Django application connect to a minio server when both are behind an nginx proxy and inside individual docker containers?

Viewed 116

Goal

Access minio server via subdomain from Django server in docker environment (both minio server and Django server are behind nginx proxy)

Expected result: Django can access minio server using the subdomain specified in nginx.

I can successfully open the minio console via s3-console.localhost

Problem

Django service can't connect properly. Docker logs:

api    | Traceback (most recent call last):
api    |   File "manage.py", line 22, in <module>
api    |     main()
api    |   File "manage.py", line 18, in main
api    |     execute_from_command_line(sys.argv)
api    |   File "/usr/local/lib/python3.8/site-packages/django/core/management/__init__.py", line 446, in execute_from_command_line
api    |     utility.execute()
api    |   File "/usr/local/lib/python3.8/site-packages/django/core/management/__init__.py", line 386, in execute        
api    |     settings.INSTALLED_APPS
api    |   File "/usr/local/lib/python3.8/site-packages/django/conf/__init__.py", line 87, in __getattr__
api    |     self._setup(name)
api    |   File "/usr/local/lib/python3.8/site-packages/django/conf/__init__.py", line 74, in _setup
api    |     self._wrapped = Settings(settings_module)
api    |   File "/usr/local/lib/python3.8/site-packages/django/conf/__init__.py", line 183, in __init__
api    |     mod = importlib.import_module(self.SETTINGS_MODULE)
api    |   File "/usr/local/lib/python3.8/importlib/__init__.py", line 127, in import_module
api    |     return _bootstrap._gcd_import(name[level:], package, level)
api    |   File "<frozen importlib._bootstrap>", line 1014, in _gcd_import
api    |   File "<frozen importlib._bootstrap>", line 991, in _find_and_load
api    |   File "<frozen importlib._bootstrap>", line 975, in _find_and_load_unlocked
api    |   File "<frozen importlib._bootstrap>", line 671, in _load_unlocked
api    |   File "<frozen importlib._bootstrap_external>", line 843, in exec_module
api    |   File "<frozen importlib._bootstrap>", line 219, in _call_with_frames_removed
api    |   File "/code/ezevent_api/settings.py", line 235, in <module>
api    |     found = client.bucket_exists(AWS_STORAGE_BUCKET_NAME)
api    |   File "/usr/local/lib/python3.8/site-packages/minio/api.py", line 658, in bucket_exists
api    |     self._execute("HEAD", bucket_name)
api    |   File "/usr/local/lib/python3.8/site-packages/minio/api.py", line 400, in _execute
api    |     region = self._get_region(bucket_name, None)
api    |   File "/usr/local/lib/python3.8/site-packages/minio/api.py", line 467, in _get_region
api    |     response = self._url_open(
api    |   File "/usr/local/lib/python3.8/site-packages/minio/api.py", line 311, in _url_open
api    |     raise InvalidResponseError(
api    | minio.error.InvalidResponseError: non-XML response from server; Response code: 500, Content-Type: text/html; charset=UTF-8, Body: <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN" "http://www.w3.org/TR/html4/loose.dtd"> 
api    | <html><head>
api    | <title>Revive Adserver</title>
api    | <meta name="generator" content="Revive Adserver v5.2.1 - http://www.revive-adserver.com">
api    | <meta name="robots" content="noindex, nofollow">
api    | <meta http-equiv="X-UA-Compatible" content="IE=7">
api    | <link rel="shortcut icon" type="image/vnd.microsoft.icon" href="https://a03.uadexchange.com/admin/assets/images/favicon.ico" />
api    | 
api    |     <link rel="stylesheet" type="text/css" href="https://a03.uadexchange.com/admin/assets/min.php?g=oxp-css-ltr&amp;v=5.2.1">
api    | 
api    | 
api    | <script type="text/javascript">
api    | <!--
api    |     var validatorPreferences = {
api    |         'strFieldContainsErrors': "The following fields contain errors:",
api    |         'strFieldFixBeforeContinue1': "Before you can continue you need",
api    |         'strFieldFixBeforeContinue2': "to correct these errors.",
api    |         'strWarningMissing': "Warning, possibly missing ",
api    |         'strWarningMissingOpening': " opening tag \'<\'",
api    |         'strWarningMissingClosing': " closing tag \'>\'",
api    |         'strSubmitAnyway': "Submit Anyway",
api    |         'thousandsSeperator': ","
api    |     };
api    | 
api    |        var tablePreferences = {
api    |                'warningBeforeDelete': false
api    |     };
api    | //-->
api    | </script>
api    | <script type="text/javascript" src="https://a03.uadexchange.com/admin/assets/min.php?g=oxp-js&amp;v=5.2.1"></script>
api    | 
api    | 
api    | 
api    | 
api    | 
api    | 
api    | 
api    | 
api    | </head>
api    | <body class="hasInterface hasGradient hasSidebar " onload="initPage();">
api    | 
api    | 
api    | 
api    | 
api    | <div id="oaHeader">
api    | 
api    |     <div id="oaHeaderBranding" class="brandingAdServer">Revive Adserver</div>
api    |   <div id="oaNavigationExtraTop">
api    |     <ul>
api    | 
api    | 
api    | 
api    | 
api    | 
api    |     </ul>
api    |     </div>
api    | 
api    | 
api    | </div>
api    | 
api    | <div id="oaNavigation">
api    |     <ul id="oaNavigationTabs">
api    |       <li class="active  first last">
api    |         <div class="left"><div class="right">
api    |           <a href="https://a03.uadexchange.com/admin/index.php" accesskey="Home">Authentication</a>
api    |         </div></div>
api    |       </li>
api    |     </ul>
api    |
api    | </div>
api    |
api    | <div id="firstLevelContent">
api    |
api    |
api    | <div id="secondLevelContent">

Context

nginx.config

user  nginx;
events {
  worker_connections   1000;
}

http {
  server {
    server_name api.lamanin.com api.localhost;
    location / {
      proxy_pass http://api:8000;
    }
  }
  server {
    server_name s3.lamanin.com s3.localhost;
    listen 80;
    location / {
       proxy_pass http://s3:9000;
       proxy_http_version 1.1;
       proxy_set_header Host $host;
       proxy_set_header X-Real-IP $remote_addr;
       proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
       proxy_set_header X-Forwarded-Proto $scheme;
    }
  }
  server {
    server_name s3-console.lamanin.com s3-console.localhost;
    listen 80;
    location / {
      proxy_pass http://s3:9090;
    }
  }
  server {
    server_name lamanin.com *.lamanin.com localhost *.localhost;
    listen 80;

    location / {
      proxy_pass http://web:3000;
    }
    
    location /ws {
      proxy_pass http://web:3000;
      proxy_http_version 1.1;
      proxy_set_header Upgrade $http_upgrade;
      proxy_set_header Connection "Upgrade";
    }
  }
}

docker-compose.yml

version: "3.8"

services:
  s3:
    image: minio/minio
    container_name: s3
    restart: on-failure
    ports:
      - "9000:9000"
      - "9090:9090"
    volumes:
      - s3:/data
    environment:
      - MINIO_ROOT_USER=${MINIO_ROOT_USER}
      - MINIO_ROOT_PASSWORD=${MINIO_ROOT_PASSWORD}
    command: server --console-address :9090 --address :9000 /data
  web:
    image: lamanin/web
    container_name: web
    environment:
      - WDS_SOCKET_PORT=${WDS_SOCKET_PORT}
      - ESLINT_NO_DEV_ERRORS=${ESLINT_NO_DEV_ERRORS}
      - REACT_APP_API_URL=${REACT_APP_API_URL}
      - REACT_APP_SHOW_COMING_SOON=${REACT_APP_SHOW_COMING_SOON}
    build:
      context: ./web
      dockerfile: Dockerfile.${ENVIRONMENT}
    restart: on-failure
    volumes:
      - web:/app/src
    ports:
      - "${WEB_PORT}"
    depends_on:
      - api
  db:
    image: postgres
    container_name: "db"
    hostname: "db"
    volumes:
      - db:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
  api:
    image: lamanin/api
    environment:
      - SECRET_KEY=${SECRET_KEY}
      - POSTGRES_DB=${POSTGRES_DB}
      - POSTGRES_USER=${POSTGRES_USER}
      - POSTGRES_PASSWORD=${POSTGRES_PASSWORD}
      - DJANGO_SUPERUSER_USERNAME=${DJANGO_SUPERUSER_USERNAME}
      - DJANGO_SUPERUSER_PASSWORD=${DJANGO_SUPERUSER_PASSWORD}
      - MINIO_URL=${MINIO_URL}
      - MINIO_DOMAIN=${MINIO_DOMAIN}
      - MINIO_SA_ACCESS_KEY=${MINIO_SA_ACCESS_KEY}
      - MINIO_SA_SECRET_KEY=${MINIO_SA_SECRET_KEY}
      - API_URL=${API_URL}
    container_name: api
    build:
      context: ./api
    restart: on-failure
    command: >
      sh -c "sleep 10 && python3 manage.py collectstatic --no-input &&
            python3 manage.py migrate &&
            python3 manage.py ensure_admin &&
            python3 manage.py runserver 0.0.0.0:8000"
    volumes:
      - api:/code
    ports:
      - "${API_PORT}"
    depends_on:
      - db
      - s3
  nginx:
    build:
      context: .
      dockerfile: Dockerfile.nginx
    container_name: "nginx"
    restart: on-failure
    depends_on:
      - api
      - web
      - s3
    ports:
      - "80:80"

volumes:
  api:
  web:
  db:
  s3:

Relevant Django settings

AWS_ACCESS_KEY_ID = os.getenv('MINIO_SA_ACCESS_KEY')
AWS_SECRET_ACCESS_KEY = os.getenv('MINIO_SA_SECRET_KEY')
AWS_STORAGE_BUCKET_NAME = 'main'
AWS_S3_ENDPOINT_URL = os.getenv('MINIO_URL')

AWS_S3_OBJECT_PARAMETERS = {
    'CacheControl': 'max-age=86400',
}

AWS_STATIC_LOCATION = 'static'
STATICFILES_STORAGE = 'ezevent_api.storage_backends.StaticStorage'
STATIC_URL = f'{AWS_S3_ENDPOINT_URL}/{AWS_STORAGE_BUCKET_NAME}/'

AWS_PUBLIC_MEDIA_LOCATION = 'media/public'
DEFAULT_FILE_STORAGE = 'ezevent_api.storage_backends.PublicMediaStorage'

AWS_PRIVATE_MEDIA_LOCATION = 'media/private'
PRIVATE_FILE_STORAGE = 'ezevent_api.storage_backends.PrivateMediaStorage'

client = Minio(
    os.getenv('MINIO_DOMAIN'),
    access_key=AWS_ACCESS_KEY_ID,
    secret_key=AWS_SECRET_ACCESS_KEY,
    secure=os.getenv('MINIO_SECURE') == 'True'
)

found = client.bucket_exists(AWS_STORAGE_BUCKET_NAME)
if not found: client.make_bucket(AWS_STORAGE_BUCKET_NAME)

Relevant Environment Variables

MINIO_URL=http://s3.localhost
MINIO_DOMAIN=s3.localhost
MINIO_ROOT_USER=admin
MINIO_ROOT_PASSWORD=[REDACTED]
MINIO_SA_ACCESS_KEY=service-account-1
MINIO_SA_SECRET_KEY=[REDACTED]
MINIO_SECURE=False

Relevant Packages

https://django-storages.readthedocs.io/en/latest/backends/amazon-S3.html

Things I Have Tried

  1. Initially I didn't use any headers in the proxy settings, so I added several headers that I found from this github thread: https://github.com/minio/minio/issues/7661.

  2. I suspected that the http://s3:9000 address isn't recognized by nginx so I used http://localhost:9000 but the localhost of nginx is probably different from the s3 service because they are in different containers.

  3. When running the s3 & nginx service using docker, and running the Django server outside of docker (running it manually using the python manage.py runserver command) it successfully connect's to the minio server if localhost:9000 is used. But failes if the subdomain http://s3.localhost is used.

0 Answers
Related