ASGI/WSGI with Channels Behind Nginx - Is my app even running?

Hi,

I am a beginner learning more about deploying Django projects with async components. I have a small grasp of a good architecture from this discussion, and am looking to learn the mechanics.

So, I have a dockerfile:

FROM --platform=linux/amd64 python:3.10.4-slim-bullseye

ENV PIP_DISABLE_PIP_VERSION_CHECK=1
ENV PYTHONDONTWRITEBYTECODE=1
ENV PYTHONUNBUFFERED=1

WORKDIR /app

COPY ./requirements.txt .

RUN pip install -r requirements.txt

COPY ./scripts /scripts
RUN chmod -R +x /scripts

ENV PATH="/scripts:/py/bin:$PATH"

COPY ./app /app

EXPOSE 8000 8001

ASGI:

import os

from channels.auth import AuthMiddlewareStack
from channels.routing import ProtocolTypeRouter, URLRouter
from channels.security.websocket import AllowedHostsOriginValidator
from django.core.asgi import get_asgi_application

os.environ.setdefault("DJANGO_SETTINGS_MODULE", "scopist.settings")
# Initialize Django ASGI application early to ensure the AppRegistry
# is populated before importing code that may import ORM models.

django_asgi_app = get_asgi_application()

from transcript.routing import websocket_urlpatterns

application = ProtocolTypeRouter(
    {
        "http": django_asgi_app,
        "websocket": AllowedHostsOriginValidator(
            AuthMiddlewareStack(URLRouter(websocket_urlpatterns))
        ),
    }
)

I have a docker-compose yaml file:

services:

  app_wsgi:
      build:
        context: .
      command: gunicorn scopist.wsgi:application --bind 127.0.0.1:8000
      volumes:
        - ./app:/app
        - dev-app-data:/vol/app
      depends_on:
        db:
          condition: service_healthy
      environment:
        - DJANGO_DEBUG=true
        - DJANGO_SECRET_KEY=django-insecure-$65qwith0x$0%w-sm+%92rt-j#t8-pdyt7a$+j-o7#1iv&te&z
        - DB_HOST=db
        - DB_NAME=postgres
        - DB_USER=postgres
        - DB_PASSWORD=postgres

  app_asgi:
    build:
      context: .
    command: daphne -b 0.0.0.0 -p 8001 scopist.asgi:application
    volumes:
      - ./app:/app
      - dev-app-data:/vol/app
    environment:
      - DJANGO_DEBUG=true
      - DJANGO_SECRET_KEY=django-insecure-$65qwith0x$0%w-sm+%92rt-j#t8-pdyt7a$+j-o7#1iv&te&z
      - DB_HOST=db
      - DB_NAME=postgres
      - DB_USER=postgres
      - DB_PASSWORD=postgres
    depends_on:
      db:
        condition: service_healthy

  db:
    image: postgres:13
    volumes:
      - dev-db-data:/var/lib/postgresql/data
    environment:
      - POSTGRES_DB=postgres
      - POSTGRES_USER=postgres
      - POSTGRES_PASSWORD=postgres
    healthcheck:
      test: ["CMD", "pg_isready", "-q", "-d", "postgres", "-U", "postgres"]
      interval: 5s
      timeout: 5s
      retries: 5

  nginx:
    image: nginx:alpine
    ports:
      - "80:80"
    volumes:
      - ./nginx.conf:/etc/nginx/conf.d
    depends_on:
      - app_wsgi
      - app_asgi

volumes:
  dev-db-data:
  dev-app-data:

And an NGINX conf:

worker_processes 1;

events {
    worker_connections 1024;
}

http {
    upstream asgi_backend {
        server app_asgi:8001;
    }

    upstream wsgi_backend {
        server app_wsgi:8000;
    }

    # For WebSocket support
    map $http_upgrade $connection_upgrade {
        default upgrade;
        ''      close;
    }

    server {
        listen 80;
        server_name localhost;

        # Route WebSocket requests (e.g., those starting with /ws/) to ASGI container
        location /ws/ {
            proxy_pass http://asgi_backend;
            proxy_http_version 1.1;
            proxy_set_header Upgrade $http_upgrade;
            proxy_set_header Connection $connection_upgrade;
            proxy_set_header Host $host;
        }

        # Route all other requests to the WSGI container
        location / {
            proxy_pass http://wsgi_backend;
            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;
        }
    }
}

I’m not quite sure what the logs are telling me:

NGINX:

2025-02-12 23:45:34 /docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration
2025-02-12 23:45:34 /docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/
2025-02-12 23:45:34 /docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh
2025-02-12 23:45:34 10-listen-on-ipv6-by-default.sh: info: /etc/nginx/conf.d/default.conf is not a file or does not exist
2025-02-12 23:45:34 /docker-entrypoint.sh: Sourcing /docker-entrypoint.d/15-local-resolvers.envsh
2025-02-12 23:45:34 /docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh
2025-02-12 23:45:34 /docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh
2025-02-12 23:45:34 /docker-entrypoint.sh: Configuration complete; ready for start up
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: using the "epoll" event method
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: nginx/1.27.4
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: built by gcc 14.2.0 (Alpine 14.2.0) 
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: OS: Linux 6.12.5-linuxkit
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: getrlimit(RLIMIT_NOFILE): 1048576:1048576
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: start worker processes
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: start worker process 20
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: start worker process 21
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: start worker process 22
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: start worker process 23
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: start worker process 24
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: start worker process 25
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: start worker process 26
2025-02-12 23:45:34 2025/02/13 06:45:34 [notice] 1#1: start worker process 27

WSGI container:

2025-02-12 23:45:34 [2025-02-13 06:45:34 +0000] [1] [INFO] Starting gunicorn 23.0.0
2025-02-12 23:45:34 [2025-02-13 06:45:34 +0000] [1] [INFO] Listening at: http://127.0.0.1:8000 (1)
2025-02-12 23:45:34 [2025-02-13 06:45:34 +0000] [1] [INFO] Using worker: sync
2025-02-12 23:45:34 [2025-02-13 06:45:34 +0000] [7] [INFO] Booting worker with pid: 7

ASGI:

2025-02-12 23:45:46 2025-02-13 06:45:46,956 INFO     Starting server at tcp:port=8001:interface=0.0.0.0
2025-02-12 23:45:46 2025-02-13 06:45:46,957 INFO     HTTP/2 support not enabled (install the http2 and tls Twisted extras)
2025-02-12 23:45:46 2025-02-13 06:45:46,958 INFO     Configuring endpoint tcp:port=8001:interface=0.0.0.0
2025-02-12 23:45:46 2025-02-13 06:45:46,960 INFO     Listening on TCP address 0.0.0.0:8001

In sum, everything seems to have been built ok, and I’m not seeing any obvious errors. But now I have no idea how to run my app. I have been messing around with ports and IPs and localhosts, and I can’t get anything to actually run.

Sorry for the dumb question, but what am I missing?

Thanks.

Welcome @cory5643 !

Have you tried pointing your browser at your Nginx instance? What happens when you try to request a page?

Note, you should:

  • Read How to deploy Django
  • Read (and do) the Deployment checklist
  • Create an nginx location for your static directory (and media if appropriate)
  • Run collectstatic
  • Run migrate (You don’t need to do a makemigrations - that should have been done earlier)

Hi @KenWhitesell - thanks for the kind welcome.

I want to close the loop on this issue. I started from scratch and rewrote some scripts. I had some typos and needed to put some work into static files and migrations - just as you suggested. I now have a perfectly running app with WSGI and ASGI components, dockerized and with NGINX directing traffic.

Onward to (Kubernetes and) deployment.

Thanks again!