diff --git a/README.md b/README.md index 7ab0b7e1..aaeb2582 100644 --- a/README.md +++ b/README.md @@ -31,7 +31,7 @@ production-ready Django projects quickly. - Optional basic ASGI setup for Websockets - Optional custom static build using Gulp or Webpack - Send emails via [Anymail](https://github.com/anymail/django-anymail) (using [Mailgun](http://www.mailgun.com/) by default or Amazon SES if AWS is selected cloud provider, but switchable) -- Media storage using Amazon S3, Google Cloud Storage or Azure Storage +- Media storage using Amazon S3, Google Cloud Storage, Azure Storage or nginx - Docker support using [docker-compose](https://github.com/docker/compose) for development and production (using [Traefik](https://traefik.io/) with [LetsEncrypt](https://letsencrypt.org/) support) - [Procfile](https://devcenter.heroku.com/articles/procfile) for deploying to Heroku - Instructions for deploying to [PythonAnywhere](https://www.pythonanywhere.com/) diff --git a/docs/deployment-with-docker.rst b/docs/deployment-with-docker.rst index a431679b..c1b8c6d7 100644 --- a/docs/deployment-with-docker.rst +++ b/docs/deployment-with-docker.rst @@ -187,3 +187,7 @@ For status check, run:: supervisorctl status +Media files without cloud provider +---------------------------------- + +If you chose no cloud provider and Docker, the media files will be served by an nginx service, from a ``production_django_media`` volume. Make sure to keep this around to avoid losing any media files. diff --git a/docs/project-generation-options.rst b/docs/project-generation-options.rst index d4ad8a9a..9b379f12 100644 --- a/docs/project-generation-options.rst +++ b/docs/project-generation-options.rst @@ -69,7 +69,7 @@ cloud_provider: 3. Azure_ 4. None - Note that if you choose no cloud provider, media files won't work. + If you choose no cloud provider and docker, the production stack will serve the media files via an nginx Docker service. Without Docker, the media files won't work. mail_service: Select an email service that Django-Anymail provides diff --git a/hooks/post_gen_project.py b/hooks/post_gen_project.py index b64bbbaf..dbc36717 100644 --- a/hooks/post_gen_project.py +++ b/hooks/post_gen_project.py @@ -491,9 +491,12 @@ def main(): use_async=("{{ cookiecutter.use_async }}".lower() == "y"), ) - if "{{ cookiecutter.cloud_provider }}" == "None": + if ( + "{{ cookiecutter.cloud_provider }}" == "None" + and "{{ cookiecutter.use_docker }}".lower() == "n" + ): print( - WARNING + "You chose not to use a cloud provider, " + WARNING + "You chose to not use any cloud providers nor Docker, " "media files won't be served in production." + TERMINATOR ) remove_storages_module() diff --git a/{{cookiecutter.project_slug}}/compose/production/nginx/Dockerfile b/{{cookiecutter.project_slug}}/compose/production/nginx/Dockerfile new file mode 100644 index 00000000..911b16f7 --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/nginx/Dockerfile @@ -0,0 +1,2 @@ +FROM nginx:1.17.8-alpine +COPY ./compose/production/nginx/default.conf /etc/nginx/conf.d/default.conf diff --git a/{{cookiecutter.project_slug}}/compose/production/nginx/default.conf b/{{cookiecutter.project_slug}}/compose/production/nginx/default.conf new file mode 100644 index 00000000..aafdd5be --- /dev/null +++ b/{{cookiecutter.project_slug}}/compose/production/nginx/default.conf @@ -0,0 +1,7 @@ +server { + listen 80; + server_name localhost; + location /media/ { + alias /usr/share/nginx/media/; + } +} diff --git a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml index cc183cd6..ea57f4a5 100644 --- a/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml +++ b/{{cookiecutter.project_slug}}/compose/production/traefik/traefik.yml @@ -57,6 +57,18 @@ http: # https://docs.traefik.io/master/routing/routers/#certresolver certResolver: letsencrypt {%- endif %} + {%- if cookiecutter.cloud_provider == 'None' %} + + web-media-router: + rule: "Host(`{{ cookiecutter.domain_name }}`) && PathPrefix(`/media/`)" + entryPoints: + - web-secure + middlewares: + - csrf + service: django-media + tls: + certResolver: letsencrypt + {%- endif %} middlewares: csrf: @@ -77,6 +89,13 @@ http: servers: - url: http://flower:5555 {%- endif %} + {%- if cookiecutter.cloud_provider == 'None' %} + + django-media: + loadBalancer: + servers: + - url: http://nginx:80 + {%- endif %} providers: # https://docs.traefik.io/master/providers/file/ diff --git a/{{cookiecutter.project_slug}}/production.yml b/{{cookiecutter.project_slug}}/production.yml index 45c8b3c1..4e9b9e04 100644 --- a/{{cookiecutter.project_slug}}/production.yml +++ b/{{cookiecutter.project_slug}}/production.yml @@ -4,6 +4,9 @@ volumes: production_postgres_data: {} production_postgres_data_backups: {} production_traefik: {} + {%- if cookiecutter.cloud_provider == 'None' %} + production_django_media: {} + {%- endif %} services: django:{% if cookiecutter.use_celery == 'y' %} &django{% endif %} @@ -24,6 +27,10 @@ services: {%- endif %} image: {{ cookiecutter.project_slug }}_production_django + {%- if cookiecutter.cloud_provider == 'None' %} + volumes: + - production_django_media:/app/{{ cookiecutter.project_slug }}/media + {%- endif %} depends_on: - postgres - redis @@ -89,3 +96,14 @@ services: volumes: - production_postgres_data_backups:/backups:z {%- endif %} + {%- if cookiecutter.cloud_provider == 'None' %} + nginx: + build: + context: . + dockerfile: ./compose/production/nginx/Dockerfile + image: {{ cookiecutter.project_slug }}_local_nginx + depends_on: + - django + volumes: + - production_django_media:/usr/share/nginx/media:ro + {%- endif %}