Getting ModuleNotFoundError: No module named '<project name>' when deploying with gunicorn

This is my gunicorn_start file inside env/bin/, by runnig this I aim to deploy my django application

#!/bin/bash

NAME='yumfed_web'
DJANGODIR=~/webapps/yumfed-web/yumfed_web
SOCKFILE=~/webapps/yumfed-web/run/gunicorn.sock
USER=webappuser
GROUP=webapps
NUM_WORKERS=3
DJANGO_SETTINGS_MODULE=yumfed_web.settingsprod
DJANGO_WSGI_MODULE=yumfed_web.wsgi
TIMEOUT=120

cd $DJANGODIR
echo "Starting $NAME as `whoami`"
source ~/webapps/yumfed-web/env/bin/activate
export DJANGO_SETTINGS_MODULE=$DJANGO_SETTINGS_MODULE
export PYTHONPATH=$DJANGODIR:$PYTHONPATH

RUNDIR=$(dirname $SOCKFILE)
test -d $RUNDIR || mkdir -p $RUNDIR

ls
exec gunicorn ${DJANGO_WSGI_MODULE}:application \
  --name $NAME \
  --workers $NUM_WORKERS \
  --timeout $TIMEOUT \
  --user=$USER --group=$GROUP \
  --bind=unix:$SOCKFILE \
  --log-level=debug \
  --log-file=-

my folder structure:

wsgi file

import os

from django.core.wsgi import get_wsgi_application

os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'yumfed_web.settings')

application = get_wsgi_application()

settingsprod.py file

"""
Django settings for yumfed_web project.

Generated by 'django-admin startproject' using Django 5.0.3.

For more information on this file, see
https://docs.djangoproject.com/en/5.0/topics/settings/

For the full list of settings and their values, see
https://docs.djangoproject.com/en/5.0/ref/settings/
"""
from dotenv import load_dotenv
import os
from pathlib import Path

load_dotenv()

# Build paths inside the project like this: BASE_DIR / 'subdir'.
BASE_DIR = Path(__file__).resolve().parent.parent


# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/5.0/howto/deployment/checklist/

# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = os.environ.get('SECRET_KEY', '')

# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = False

ALLOWED_HOSTS = ['*']

INTERNAL_IPS = [
    "127.0.0.1",
]

# Application definition

INSTALLED_APPS = [
    'django.contrib.admin',
    'django.contrib.auth',
    'django.contrib.contenttypes',
    'django.contrib.sessions',
    'django.contrib.messages',
    'django.contrib.staticfiles',

    'tailwind',
    'django_browser_reload',
    'fontawesomefree',
    'ckeditor',
    'solo',

    'theme',
    'members',
    'news',
    'scholarship',
    'internship',
    'memories',
]

MIDDLEWARE = [
    'django.middleware.security.SecurityMiddleware',
    'django.contrib.sessions.middleware.SessionMiddleware',
    'django.middleware.common.CommonMiddleware',
    'django.middleware.csrf.CsrfViewMiddleware',
    'django.contrib.auth.middleware.AuthenticationMiddleware',
    'django.contrib.messages.middleware.MessageMiddleware',
    'django.middleware.clickjacking.XFrameOptionsMiddleware',

    "django_browser_reload.middleware.BrowserReloadMiddleware",
]

ROOT_URLCONF = 'yumfed_web.urls'

TEMPLATES = [
    {
        'BACKEND': 'django.template.backends.django.DjangoTemplates',
        'DIRS': [
            BASE_DIR / 'templates',
        ],
        'APP_DIRS': True,
        'OPTIONS': {
            'context_processors': [
                'django.template.context_processors.debug',
                'django.template.context_processors.request',
                'django.contrib.auth.context_processors.auth',
                'django.contrib.messages.context_processors.messages',
            ],
        },
    },
]

# Register the generated 'theme' app
TAILWIND_APP_NAME = 'theme'

WSGI_APPLICATION = 'yumfed_web.wsgi.application'


# Database
# https://docs.djangoproject.com/en/5.0/ref/settings/#databases

DATABASES = {
    'default': {
        #'ENGINE': 'django.db.backends.sqlite3',
        #'NAME': BASE_DIR / 'db.sqlite3',

        'ENGINE': 'django.db.backends.postgresql_psycopg2',
        'NAME': 'yumfed',
        'USER': 'yumfeduser',
        'PASSWORD': os.environ.get('DB_PASSWORD', ''),
        'HOST': 'localhost',
        'PORT': '',
    }
}


# Password validation
# https://docs.djangoproject.com/en/5.0/ref/settings/#auth-password-validators

AUTH_PASSWORD_VALIDATORS = [
    {
        'NAME': 'django.contrib.auth.password_validation.UserAttributeSimilarityValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.CommonPasswordValidator',
    },
    {
        'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator',
    },
]

AUTH_USER_MODEL = 'members.CustomUser'

AUTHENTICATION_BACKENDS = ['members.backends.EmailBackend']


# Internationalization
# https://docs.djangoproject.com/en/5.0/topics/i18n/

LANGUAGE_CODE = 'en-us'

TIME_ZONE = 'UTC'

USE_I18N = True

USE_TZ = True


# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/5.0/howto/static-files/

STATIC_URL = 'static/'

STATICFILES_DIRS = [
    BASE_DIR / "static",
]

MEDIA_URL = '/media/'

MEDIA_ROOT = BASE_DIR / "media"

# Default primary key field type
# https://docs.djangoproject.com/en/5.0/ref/settings/#default-auto-field

DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'

and error I get

Starting yumfed_web as root
internship  members   news		scholarship  templates	yumfed_web
manage.py   memories  requirements.txt	static	    theme
[2024-05-17 10:57:21 +0000] [56611] [DEBUG] Current configuration:
  config: ./gunicorn.conf.py
  wsgi_app: None
  bind: ['unix:/root/webapps/yumfed-web/run/gunicorn.sock']
  backlog: 2048
  workers: 3
  worker_class: sync
  threads: 1
  worker_connections: 1000
  max_requests: 0
  max_requests_jitter: 0
  timeout: 120
  graceful_timeout: 30
  keepalive: 2
  limit_request_line: 4094
  limit_request_fields: 100
  limit_request_field_size: 8190
  reload: False
  reload_engine: auto
  reload_extra_files: []
  spew: False
  check_config: False
  print_config: False
  preload_app: False
  sendfile: None
  reuse_port: False
  chdir: /root/webapps/yumfed-web/yumfed_web
  daemon: False
  raw_env: []
  pidfile: None
  worker_tmp_dir: None
  user: 999
  group: 988
  umask: 0
  initgroups: False
  tmp_upload_dir: None
  secure_scheme_headers: {'X-FORWARDED-PROTOCOL': 'ssl', 'X-FORWARDED-PROTO': 'https', 'X-FORWARDED-SSL': 'on'}
  forwarded_allow_ips: ['127.0.0.1']
  accesslog: None
  disable_redirect_access_to_syslog: False
  access_log_format: %(h)s %(l)s %(u)s %(t)s "%(r)s" %(s)s %(b)s "%(f)s" "%(a)s"
  errorlog: -
  loglevel: debug
  capture_output: False
  logger_class: gunicorn.glogging.Logger
  logconfig: None
  logconfig_dict: {}
  logconfig_json: None
  syslog_addr: udp://localhost:514
  syslog: False
  syslog_prefix: None
  syslog_facility: user
  enable_stdio_inheritance: False
  statsd_host: None
  dogstatsd_tags: 
  statsd_prefix: 
  proc_name: yumfed_web
  default_proc_name: yumfed_web.wsgi:application
  pythonpath: None
  paste: None
  on_starting: <function OnStarting.on_starting at 0x79836f367920>
  on_reload: <function OnReload.on_reload at 0x79836f367a60>
  when_ready: <function WhenReady.when_ready at 0x79836f367ba0>
  pre_fork: <function Prefork.pre_fork at 0x79836f367d80>
  post_fork: <function Postfork.post_fork at 0x79836f367ec0>
  post_worker_init: <function PostWorkerInit.post_worker_init at 0x79836f38c040>
  worker_int: <function WorkerInt.worker_int at 0x79836f38c180>
  worker_abort: <function WorkerAbort.worker_abort at 0x79836f38c2c0>
  pre_exec: <function PreExec.pre_exec at 0x79836f38c400>
  pre_request: <function PreRequest.pre_request at 0x79836f38c540>
  post_request: <function PostRequest.post_request at 0x79836f38c5e0>
  child_exit: <function ChildExit.child_exit at 0x79836f38c720>
  worker_exit: <function WorkerExit.worker_exit at 0x79836f38c860>
  nworkers_changed: <function NumWorkersChanged.nworkers_changed at 0x79836f38c9a0>
  on_exit: <function OnExit.on_exit at 0x79836f38cae0>
  ssl_context: <function NewSSLContext.ssl_context at 0x79836f38ccc0>
  proxy_protocol: False
  proxy_allow_ips: ['127.0.0.1']
  keyfile: None
  certfile: None
  ssl_version: 2
  cert_reqs: 0
  ca_certs: None
  suppress_ragged_eofs: True
  do_handshake_on_connect: False
  ciphers: None
  raw_paste_global_conf: []
  strip_header_spaces: False
  permit_unconventional_http_method: False
  permit_unconventional_http_version: False
  casefold_http_method: False
  header_map: drop
  tolerate_dangerous_framing: False
[2024-05-17 10:57:21 +0000] [56611] [INFO] Starting gunicorn 22.0.0
[2024-05-17 10:57:21 +0000] [56611] [DEBUG] Arbiter booted
[2024-05-17 10:57:21 +0000] [56611] [INFO] Listening at: unix:/root/webapps/yumfed-web/run/gunicorn.sock (56611)
[2024-05-17 10:57:21 +0000] [56611] [INFO] Using worker: sync
[2024-05-17 10:57:21 +0000] [56615] [INFO] Booting worker with pid: 56615
[2024-05-17 10:57:21 +0000] [56615] [ERROR] Exception in worker process
Traceback (most recent call last):
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/arbiter.py", line 609, in spawn_worker
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/workers/base.py", line 134, in init_process
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/workers/base.py", line 146, in load_wsgi
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/app/base.py", line 67, in wsgi
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/app/wsgiapp.py", line 58, in load
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/app/wsgiapp.py", line 48, in load_wsgiapp
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/util.py", line 371, in import_app
  File "/usr/lib/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1324, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'yumfed_web'
[2024-05-17 10:57:21 +0000] [56615] [INFO] Worker exiting (pid: 56615)
[2024-05-17 10:57:21 +0000] [56616] [INFO] Booting worker with pid: 56616
[2024-05-17 10:57:21 +0000] [56616] [ERROR] Exception in worker process
Traceback (most recent call last):
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/arbiter.py", line 609, in spawn_worker
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/workers/base.py", line 134, in init_process
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/workers/base.py", line 146, in load_wsgi
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/app/base.py", line 67, in wsgi
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/app/wsgiapp.py", line 58, in load
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/app/wsgiapp.py", line 48, in load_wsgiapp
  File "/root/webapps/yumfed-web/env/lib/python3.12/site-packages/gunicorn/util.py", line 371, in import_app
  File "/usr/lib/python3.12/importlib/__init__.py", line 90, in import_module
    return _bootstrap._gcd_import(name[level:], package, level)
           ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1310, in _find_and_load_unlocked
  File "<frozen importlib._bootstrap>", line 488, in _call_with_frames_removed
  File "<frozen importlib._bootstrap>", line 1387, in _gcd_import
  File "<frozen importlib._bootstrap>", line 1360, in _find_and_load
  File "<frozen importlib._bootstrap>", line 1324, in _find_and_load_unlocked
ModuleNotFoundError: No module named 'yumfed_web'
[2024-05-17 10:57:21 +0000] [56616] [INFO] Worker exiting (pid: 56616)
[2024-05-17 10:57:21 +0000] [56611] [ERROR] Worker (pid:56615) exited with code 3
[2024-05-17 10:57:21 +0000] [56611] [ERROR] Worker (pid:56616) was sent SIGTERM!
[2024-05-17 10:57:21 +0000] [56611] [ERROR] Shutting down: Master
[2024-05-17 10:57:21 +0000] [56611] [ERROR] Reason: Worker failed to boot.

when I run gunicorn yumfed_web.wsgi:application from ~/webapps/yumfed-web directory it runs without errors

The first possibility that comes to mind is a permissions issue. When you run this manually, what user are you logged in as? Does UID 999, GID 988 have all the necessary permissions into the project directory structure?

I am logged in as root when running the script, as you can see in output “whoami” returns root


directories are owned by webappuser

I changed my user and group form webappuser, webapps to root, root in gunicorn_start file and it run properly. So as @KenWhitesell mentioned it is, indeed, permission issue. But I cannot think of any solution to this issue.

Check through all the files and directories in your project to ensure that everything is owned by webappuser.


wsgi.py is not executable, can it be a reason?

No, your python code does not need to be executable.

I just realized that your webapps directory is in /root. I’m willing to guarantee that webapps does not have the necessary permissions to /root. You should have all this in something like /home/webappuser/webapps (or /opt/webapps, etc). Your project should not reside in someone else’s home directory, and almost certainly not in /root.

as you advised I moved project and venv to /webapps directory and it solved the issue, Thanks very much. So, the lesson I need to learn is not to put projects inside /root folder, as it is unsecure an will not work.