Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Images not loading when using nginx proxy manager #452

Closed
moorsey opened this issue Feb 23, 2021 · 22 comments
Closed

Images not loading when using nginx proxy manager #452

moorsey opened this issue Feb 23, 2021 · 22 comments
Labels
documentation Improvements or additions to documentation setup issue possibly or definitely an issue with the user setup

Comments

@moorsey
Copy link

moorsey commented Feb 23, 2021

Version

Please provide your current version (can be found on the system page since v0.8.4)
Version: 0.14.3

Issue

Images fail to load, guessing something to do with my proxy config

So I modified the example docker compose (not hugely familiar with docker compose, normally spin up with "docker run -d" commands, but learning!

I already run nginx proxy manager, so didn't need to nginx config in the example and stripped it out. DB and tandoor images in their own network and tandoor also joined to my existing proxy network, this all works fine. Have a feeling I may have removed something I shouldn't though, there is a media volume on the nginx image in the example, but wasn't sure how that worked

Images that fail to load have a path of "https://mydomain.org/media/recipes/cbff09ab-5789-400d-acd3-c7ce30f246b7_12.png" in the URL

.env

Please include your .env config file (make sure to remove/replace all secrets)

env content
```# only set this to true when testing/debugging
# when unset: 1 (true) - dont unset this, just for development
DEBUG=0

# hosts the application can run under e.g. recipes.mydomain.com,cooking.mydomain.com,...
ALLOWED_HOSTS=*

# random secret key, use for example `base64 /dev/urandom | head -c50` to generate one
SECRET_KEY=****

# your default timezone See https://timezonedb.com/time-zones for a list of timezones
TIMEZONE=Europe/London

# add only a database password if you want to run with the default postgres, otherwise change settings accordingly
DB_ENGINE=django.db.backends.postgresql
POSTGRES_HOST=db_recipes
POSTGRES_PORT=5432
POSTGRES_USER=djangouser
POSTGRES_PASSWORD=****
POSTGRES_DB=djangodb

# the default value for the user preference 'fractions' (enable/disable fraction support)
# default: disabled=0
FRACTION_PREF_DEFAULT=0

# the default value for the user preference 'comments' (enable/disable commenting system)
# default comments enabled=1
COMMENT_PREF_DEFAULT=1

# Users can set a amount of time after which the shopping list is refreshed when they are in viewing mode
# This is the minimum interval users can set. Setting this to low will allow users to refresh very frequently which
# might cause high load on the server. (Technically they can obviously refresh as often as they want with their own scripts)
SHOPPING_MIN_AUTOSYNC_INTERVAL=5

# Default for user setting sticky navbar
#STICKY_NAV_PREF_DEFAULT=1

# If staticfiles are stored at a different location uncomment and change accordingly
# STATIC_URL=/static/

# If mediafiles are stored at a different location uncomment and change accordingly
# MEDIA_URL=/media/

# Serve mediafiles directly using gunicorn. Basically everyone recommends not doing this. Please use any of the examples
# provided that include an additional nxginx container to handle media file serving.
# If you know what you are doing turn this back on (1) to serve media files using djangos serve() method.
# when unset: 1 (true) - this is temporary until an appropriate amount of time has passed for everyone to migrate
GUNICORN_MEDIA=0

# allow authentication via reverse proxy (e.g. authelia), leave off if you dont know what you are doing
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
# when unset: 0 (false)
REVERSE_PROXY_AUTH=0


# allows you to setup OAuth providers
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
# SOCIAL_PROVIDERS = allauth.socialaccount.providers.github, allauth.socialaccount.providers.nextcloud,



### `docker-compose.yml`
When running with docker compose please provide your `docker-compose.yml`

docker-compose.yml content

services:
  db_recipes:
    restart: always
    image: postgres:11-alpine
    volumes:
      - ./postgresql:/var/lib/postgresql/data
    env_file:
      - ./.env

  web_recipes:
    image: vabene1111/recipes
    restart: always
    env_file:
      - ./.env
    volumes:
      - staticfiles:/opt/recipes/staticfiles
      - ./mediafiles:/opt/recipes/mediafiles
    depends_on:
      - db_recipes
    networks:
      - default
      - proxy

networks:
  default:
  proxy:
   external: true

volumes:
  staticfiles:

@moorsey moorsey added the setup issue possibly or definitely an issue with the user setup label Feb 23, 2021
@vabene1111
Copy link
Collaborator

If you strip out the nginx container then you need to make sure that the functions that it fulfills (which is mainly serving as a web server for gunicorn which your proxy manager does but also as a server for media files [which you need to explicitly configure it to do]) is replaced.

Please take a look at the path configurations of the provided nginx config file and make sure that the /media folder is served by your proxy or just add the nginx image back in as it causes minimal/negligible performance impacts and simplifies a lot of things.

@auanasgheps
Copy link
Contributor

auanasgheps commented Feb 23, 2021

Hi there you look familiar #409
I would let you in but I can't find the port

(door and port in my language have the same meaning)

@moorsey
Copy link
Author

moorsey commented Feb 27, 2021

@vabene1111 Thanks for the reply.

I'll be honest, I don't fully understand. Tandoor seems fairly unique in this setup, no other docker I am using comes with it's own container for reverse proxy as a requirement, I just don't see how that works with folks who have an existing reverse proxy setup, without duplicating capabilities, or how I would server this folder via nginx proxy manager. But of course am a bit of a beginner in the grand scheme of things, just not something I have come across in setting 20 off other containers via my reverse proxy

Will of course give it a try, am enjoying the app and do very much want it to work correctly

@auanasgheps Thanks for the link, will have a look

@vabene1111
Copy link
Collaborator

I totally understand your confusion @moorsey

The nginx container bundled with the application is NOT a reverse proxy or at least not only.

Gunicorn, the program executing the python functions that make up Tandoor is not intended to run as the outside facing web server according to its developers. A simple reverse proxy wont fix this, at least not for the media files.
That is why the nginx container is included in the default setup. It takes the requests from your reverse proxy or directly from the outside network. It then routes all media requests internally and all page requests to gunicorn.

The additional nginx container causes only minmal overhead and is therefor not really a problem, you can however remove it if you want to. For this simple enable the environment variable GUNICORN_MEDIA. This will let gunicorn serve your media files as well.

The main thing that is the problem with the issue discussed here are the headers. Django does some magic to determine where a requests comes from and then return correct urls. In the frontend this is easy since everything is relative. In the API, which is used by the recipe view, the image is loaded from the API call which itself needs to return absolute paths (since the API could technically be run under a different subdomain or whatever).

When developing the new recipe view some beta users pointed out that they could not see the image when they were using a non default port or https. Both issues could be resolved by setting the appropriate headers in the example nginx config

The problem is here is where my understanding of the Problem ends and where it is hard to me to follow your Problems as i do not have the same setups running as you do.

The issue is most likely related to the headers not correctly being passed to the django application trough the chain of webservers/reverse proxys.

@auanasgheps @moorsey while wiriting this i did a little research, turns out you should be able to fix the issue by setting the MEDIA_URL in settings which is already supported trough envrionment variables.

Please try to set it to the absoulute URL including the protocol and port and the /media/recipes at the end and tell me if it works.

https://github.com/vabene1111/recipes/blob/develop/.env.template#L43

respondcreate/django-versatileimagefield#11

@auanasgheps
Copy link
Contributor

@vabene1111 IT FREAKING WORKS
Thank you!

For more clarity

# If mediafiles are stored at a different location uncomment and change accordingly
MEDIA_URL=https://recipes.example.com:8080/media/

I am going to close my issue as well. Let's update the documentation.

@vabene1111
Copy link
Collaborator

i will leave this issue open to remind myself to update the documentation accordingly

@moorsey
Copy link
Author

moorsey commented Mar 4, 2021

@vabene1111 thanks so much for the reply and extra info, really appreciated. Will be getting into this at the weekend, so hopefully reply properly then

@auanasgheps have had a look through your issue also, would you mind sharing your compose file for tandoor, just so I can compare and contrast to get the right fix on my end with this extra info

Many thanks all

@auanasgheps
Copy link
Contributor

auanasgheps commented Mar 4, 2021

Hi, my compose is pretty straightforward.
I use a single centralized nginx container to proxy all the services/containers I need.

Either way the issue is fixed for me using the fix I mentioned above.

version: "2"
services:
  recipes-db:
    restart: unless-stopped
    image: postgres:11-alpine
    container_name: recipes-db
    volumes:
      - /.../docker-apps/recipes/db:/var/lib/postgresql/data
    env_file: /.../recipes.env
    networks:
      - rete  

  recipes-app:
    image: vabene1111/recipes
    restart: unless-stopped
    container_name: recipes-app
    env_file: /.../recipes.env
    volumes:
      - /.../docker-apps/recipes/staticfiles:/opt/recipes/staticfiles
      - /.../docker-apps/recipes/mediafiles:/opt/recipes/mediafiles
    depends_on:
      - recipes-db
    ports:
      - 2310:8080
    networks:
      - rete  

networks:
    rete:
        external:
            name: rete

@moorsey
Copy link
Author

moorsey commented Mar 20, 2021

Thanks for sharing this @auanasgheps got me on the right track, I think!

Have pretty much gone back to the example file, to use the provided nginx container, I didn't fancy re-spinning my existing nginx container just now, to also mount the tandoor media files to allow this to work as you managed. I have joined the tandoor nginx container to my existing nginx proxy manager network and created an entry with sub domain etc.

image

Now unfortunately, I just get a "Welcome to nginx" screen from the tandoor nginx container and never get to the web app

Not sure if this is a clash between two nginx setups

compose file

version: "2"
services:
  db_recipes:
    restart: unless-stopped
    image: postgres:11-alpine
    volumes:
      - ./postgresql:/var/lib/postgresql/data
    env_file:
      - ./.env

  web_recipes:
    image: vabene1111/recipes
    restart: unless-stopped
    env_file:
      - ./.env
    volumes:
      - staticfiles:/opt/recipes/staticfiles
      - ./mediafiles:/opt/recipes/mediafiles
    depends_on:
      - db_recipes
    networks:
      - default

  nginx_recipes:
    image: nginx:mainline-alpine
    restart: unless-stopped
    env_file:
      - ./.env
    depends_on:
      - web_recipes
    volumes:
      - nginx_config:/etc/nginx/conf.d:ro
      - staticfiles:/static
      - ./mediafiles:/media
    networks:
      - default
      - proxy
 
networks:
  default:
  proxy:
   external: true

volumes:
  nginx_config:
  staticfiles:

Logs from tandoor_nginx_recipes_1 container

/docker-entrypoint.sh: /docker-entrypoint.d/ is not empty, will attempt to perform configuration,
/docker-entrypoint.sh: Looking for shell scripts in /docker-entrypoint.d/,
/docker-entrypoint.sh: Launching /docker-entrypoint.d/10-listen-on-ipv6-by-default.sh,
10-listen-on-ipv6-by-default.sh: info: can not modify /etc/nginx/conf.d/default.conf (read-only file system?),
/docker-entrypoint.sh: Launching /docker-entrypoint.d/20-envsubst-on-templates.sh,
/docker-entrypoint.sh: Launching /docker-entrypoint.d/30-tune-worker-processes.sh,
/docker-entrypoint.sh: Configuration complete; ready for start up,
172.22.0.11 - - [20/Mar/2021:11:15:10 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
2021/03/20 11:15:10 [error] 22#22: *2 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.22.0.11, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "tandoor.mydomain.org", referrer: "https://tandoor.mydomain.org/",
172.22.0.11 - - [20/Mar/2021:11:15:10 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "https://tandoor.mydomain.org/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
172.22.0.11 - - [20/Mar/2021:11:15:11 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
172.22.0.11 - - [20/Mar/2021:11:15:11 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "https://tandoor.mydomain.org/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
2021/03/20 11:15:11 [error] 22#22: *4 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.22.0.11, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "tandoor.mydomain.org", referrer: "https://tandoor.mydomain.org/",
172.22.0.11 - - [20/Mar/2021:11:15:11 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
2021/03/20 11:15:12 [error] 22#22: *6 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.22.0.11, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "tandoor.mydomain.org", referrer: "https://tandoor.mydomain.org/",
172.22.0.11 - - [20/Mar/2021:11:15:12 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "https://tandoor.mydomain.org/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
172.22.0.11 - - [20/Mar/2021:11:15:12 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
172.22.0.11 - - [20/Mar/2021:11:15:12 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "https://tandoor.mydomain.org/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
2021/03/20 11:15:12 [error] 22#22: *8 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.22.0.11, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "tandoor.mydomain.org", referrer: "https://tandoor.mydomain.org/",
172.22.0.11 - - [20/Mar/2021:11:15:12 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
172.22.0.11 - - [20/Mar/2021:11:15:12 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "https://tandoor.mydomain.org/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
2021/03/20 11:15:12 [error] 22#22: *10 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.22.0.11, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "tandoor.mydomain.org", referrer: "https://tandoor.mydomain.org/",
172.22.0.11 - - [20/Mar/2021:11:15:12 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
2021/03/20 11:15:12 [error] 22#22: *12 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.22.0.11, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "tandoor.mydomain.org", referrer: "https://tandoor.mydomain.org/",
172.22.0.11 - - [20/Mar/2021:11:15:12 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "https://tandoor.mydomain.org/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
172.22.0.11 - - [20/Mar/2021:11:15:12 +0000] "GET / HTTP/1.1" 200 612 "-" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
172.22.0.11 - - [20/Mar/2021:11:15:13 +0000] "GET /favicon.ico HTTP/1.1" 404 555 "https://tandoor.mydomain.org/" "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.90 Safari/537.36" "81.174.148.37",
2021/03/20 11:15:13 [error] 22#22: *14 open() "/usr/share/nginx/html/favicon.ico" failed (2: No such file or directory), client: 172.22.0.11, server: localhost, request: "GET /favicon.ico HTTP/1.1", host: "tandoor.mydomain.org", referrer: "https://tandoor.mydomain.org/",

.env

# only set this to true when testing/debugging
# when unset: 1 (true) - dont unset this, just for development
DEBUG=1

# hosts the application can run under e.g. recipes.mydomain.com,cooking.mydomain.com,...
ALLOWED_HOSTS=*

# random secret key, use for example `base64 /dev/urandom | head -c50` to generate one
SECRET_KEY=blah

# your default timezone See https://timezonedb.com/time-zones for a list of timezones
TIMEZONE=Europe/London

# add only a database password if you want to run with the default postgres, otherwise change settings accordingly
DB_ENGINE=django.db.backends.postgresql
POSTGRES_HOST=db_recipes
POSTGRES_PORT=5432
POSTGRES_USER=djangouser
POSTGRES_PASSWORD=djangouser
POSTGRES_DB=djangodb

# the default value for the user preference 'fractions' (enable/disable fraction support)
# default: disabled=0
FRACTION_PREF_DEFAULT=0

# the default value for the user preference 'comments' (enable/disable commenting system)
# default comments enabled=1
COMMENT_PREF_DEFAULT=1

# Users can set a amount of time after which the shopping list is refreshed when they are in viewing mode
# This is the minimum interval users can set. Setting this to low will allow users to refresh very frequently which
# might cause high load on the server. (Technically they can obviously refresh as often as they want with their own scripts)
SHOPPING_MIN_AUTOSYNC_INTERVAL=5

# Default for user setting sticky navbar
#STICKY_NAV_PREF_DEFAULT=1

# If staticfiles are stored at a different location uncomment and change accordingly
# STATIC_URL=/static/

# If mediafiles are stored at a different location uncomment and change accordingly
#MEDIA_URL=

# Serve mediafiles directly using gunicorn. Basically everyone recommends not doing this. Please use any of the examples
# provided that include an additional nxginx container to handle media file serving.
# If you know what you are doing turn this back on (1) to serve media files using djangos serve() method.
# when unset: 1 (true) - this is temporary until an appropriate amount of time has passed for everyone to migrate
GUNICORN_MEDIA=0

# allow authentication via reverse proxy (e.g. authelia), leave off if you dont know what you are doing
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
# when unset: 0 (false)
REVERSE_PROXY_AUTH=0


# allows you to setup OAuth providers
# see docs for more information https://vabene1111.github.io/recipes/features/authentication/
# SOCIAL_PROVIDERS = allauth.socialaccount.providers.github, allauth.socialaccount.providers.nextcloud,

@auanasgheps
Copy link
Contributor

What happens if you point directly to the tandoor nginx? Does it work as expected?

@moorsey
Copy link
Author

moorsey commented Mar 21, 2021

Thanks @auanasgheps

Thought came to me this morning, I had mistakenly thought that web access was via the tandoor nginx container, so had pointed my nginx proxy manager there, where as actually it should have been pointed to the tandoor web container

All working with images now!

Won't close this as know @vabene1111 wanted it open for doc update reminder, but my issue is solved now

Appreciate both your assistance and explanations

@vabene1111 vabene1111 added the documentation Improvements or additions to documentation label Mar 21, 2021
@breakingflower
Copy link

breakingflower commented Apr 10, 2021

So I'm running into a similar issue. Can someone please guide me through the process of serving media if I am behind a linuxserver/swag reverse proxy?

I have mounted my media location in the container: ${SSD_APPDATA_ROOT}/recipes/media:/opt/recipes/mediafiles
I have set up the proxy-conf to be as follows:

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name recipes.*;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    # enable for ldap auth, fill in ldap details in ldap.conf
    #include /config/nginx/ldap.conf;

    # enable for Authelia
    #include /config/nginx/authelia-server.conf;
	
    # serve media files
    location /media/ {
        alias /opt/recipes/mediafiles/;
    }

    location / {
        # enable the next two lines for http auth
        #auth_basic "Restricted";
        #auth_basic_user_file /config/nginx/.htpasswd;

        # enable the next two lines for ldap auth
        #auth_request /auth;
        #error_page 401 =200 /ldaplogin;

        # enable for Authelia
        #include /config/nginx/authelia-location.conf;

        include /config/nginx/proxy.conf;
        resolver 127.0.0.11 valid=30s;
        set $upstream_app recipes;
        set $upstream_port 8080;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
        
    }
}

Images are being uploaded correctly, but I can't seem to get the reverse proxy to point to the correct file on system. I get a 404 because the location is not found:
GET https://recipes.example.com/media/recipes/74d6acd8-fc7c-4624-8e95-2030fbdb13f5_1.png 404

Do I need to mount the /opt/recipes/mediafiles in the swag container? It does not seem to change anything.

I've also edited my MEDIA_URL as followS:
MEDIA_URL=https://recipes.example.com/media/

@vabene1111
Copy link
Collaborator

So it looks like the frontend is trying to access the correct url. If you serve the mediafiles trough swag then yes, mediafiles definitely needs to be accessible there. Not only that but the swag container also needs to serve the directory under the correct url but that looks ok in cour config above (given that /opt/recipes/mediafiles is mounted at exactly that path in the proxy container)

As with basically everyone who had this problem before you i HIGHLY reccomend just leaving the nginx container from the default example in the docker stack, it does not take much resources and helps ease a lot of setup problems.

@auanasgheps
Copy link
Contributor

auanasgheps commented Apr 10, 2021

Yes, I recommend the default approach which is

  • Following the docs, use a dedicated and dumb nginx container to serve media and static assets
  • Direct your beefy and real nginx container to the dumb one without having to mount folders.

You might have to add one of the following to the main nginx config or you'll get login errors:
proxy_set_header X-Forwarded-Proto $scheme;
or
proxy_set_header Host $http_host;
Looks like you're using swag, so you'll need the former.

@breakingflower
Copy link

It's actually very simple, although you need to mount the media directory in the correct path.

Example functional compose:

recipes:
  image: vabene1111/recipes
  container_name: recipes
  restart: unless-stopped
  env_file:
    - ./recipes/.env
  environment:
    - UID=${PUID}
    - GID=${PGID}
    - TZ=${TIMEZONE}
  volumes:
    - ./recipes/static:/opt/recipes/staticfiles
    - ./recipes/media:/opt/recipes/mediafiles
  ports:
    - "${RECIPES_PORT}:8080"
  depends_on:
    - recipes_db

recipes_db:
  restart: unless-stopped
  container_name: recipes_db
  image: arm64v8/postgres:alpine
  volumes:
    - ./recipes/db:/var/lib/postgresql/data
  env_file:
    - ./recipes/.env

swag:
  image: ghcr.io/linuxserver/swag
  container_name: swag
  cap_add:
    - NET_ADMIN
  environment:
    - PUID=1000
    - PGID=1000
    - TZ=${TZ}
    - URL=example.com
    - SUBDOMAINS=wildcard
    - VALIDATION=dns
    - DNSPLUGIN=route53 #optional
    - EMAIL=${ADMIN_EMAIL} #optional
    - ONLY_SUBDOMAINS=false #optional
    - STAGING=false #optional
  volumes:
    - ./swag:/config
    # for recipes media
    - ./recipes/media:/media
  ports:
    - 443:443
    - 80:80 #optional
  restart: unless-stopped

Your nginx config for swag can be the normal one:

## Version 2021/01/21
# make sure that your dns has a cname set for recipes
# make sure to mount /media/ in your swag container to point to your Recipes Media directory

# if using Authelia use this one:
# Doc: https://vabene1111.github.io/recipes/install/docker/#using-proxy-authentication

server {
    listen 443 ssl;
    listen [::]:443 ssl;

    server_name recipes.*;

    include /config/nginx/ssl.conf;

    client_max_body_size 0;

    # enable for ldap auth, fill in ldap details in ldap.conf
    #include /config/nginx/ldap.conf;

    # enable for Authelia
    #include /config/nginx/authelia-server.conf;
	
    # serve media files
    location /media {
        alias /media/;
    }

    location / {
        # enable the next two lines for http auth
        #auth_basic "Restricted";
        #auth_basic_user_file /config/nginx/.htpasswd;

        # enable the next two lines for ldap auth
        #auth_request /auth;
        #error_page 401 =200 /ldaplogin;

        # enable for Authelia
        #include /config/nginx/authelia-location.conf;

        include /config/nginx/proxy.conf;
        resolver 127.0.0.11 valid=30s;
        set $upstream_app recipes;
        set $upstream_port 8080;
        set $upstream_proto http;
        proxy_pass $upstream_proto://$upstream_app:$upstream_port;
        
    }
}

Finally, your .env file should contain the location of the media:

# If mediafiles are stored at a different location uncomment and change accordingly
MEDIA_URL=https://example.com/media/

@auanasgheps
Copy link
Contributor

auanasgheps commented Apr 26, 2021

I'm not going to mess with my setup which now works, but last time I tried this it still wasn't working. I'm glad it does for you!

@Dulanic
Copy link

Dulanic commented May 12, 2021

The only point of clarification to @fl0r1s is that the MEDIA_URL should prob be MEDIA_URL=https://recipes.example.com/media/
in order to keep inline /w his other code. That is where I got mixed up.

@mlabuhn
Copy link

mlabuhn commented Nov 25, 2022

So I have now also run into this issue, and the solutions above do not seem to work for me. I am using the recommended setup with a bundled nginx server, volumes instead of bind mounts throughout, and an nginx reverse proxy in front of the application.

I have tried substituting different values for MEDIA_URL, including https://<FQDN>/media/ and https://<FQDN>/media/recipes/, but none of that makes any difference, I just keep seeing 404 errors for any media files in the logs.

Here are all the relevant files and configs:

.env

DEBUG=0
SQL_DEBUG=0
TANDOOR_PORT=8080
ALLOWED_HOSTS=*
SECRET_KEY=<hidden>
TIMEZONE=Europe/London
DB_ENGINE=django.db.backends.postgresql
POSTGRES_HOST=db_recipes
POSTGRES_PORT=5432
POSTGRES_USER=<hidden>
POSTGRES_PASSWORD=<hidden>
POSTGRES_DB=tandoor
FRACTION_PREF_DEFAULT=0
COMMENT_PREF_DEFAULT=1
SHOPPING_MIN_AUTOSYNC_INTERVAL=5
GUNICORN_MEDIA=0
REVERSE_PROXY_AUTH=0
MEDIA_URL=/media/

docker-compose.yml

version: "3"
services:
  db_recipes:
    restart: unless-stopped
    image: postgres:11-alpine
    volumes:
      - tandoor_postgres:/var/lib/postgresql/data
    env_file:
      - stack.env

  web_recipes:
    restart: unless-stopped
    image: vabene1111/recipes
    ports:
      - 8580:8080
    env_file:
      - stack.env
    volumes:
      - tandoor_static:/opt/recipes/staticfiles
      - tandoor_media:/opt/recipes/mediafiles
      - tandoor_nginx:/opt/recipes/nginx/conf.d
    depends_on:
      - db_recipes

  nginx_recipes:
    restart: unless-stopped
    image: nginx:mainline-alpine
    env_file:
      - stack.env
    depends_on:
      - web_recipes
    volumes:
      - tandoor_nginx:/etc/nginx/conf.d:ro
      - tandoor_static:/static:ro
      - tandoor_media:/media:ro

volumes:
  tandoor_postgres:
  tandoor_static:
  tandoor_media:
  tandoor_nginx:

nginx.conf (virtual host)

server {
    # server port and name
    server_name  <FQDN>;

    access_log  /var/log/nginx/tandoor/access.log;
    error_log   /var/log/nginx/tandoor/error.log;

    location / {
        allow               <local network>;
        deny                all;
        proxy_pass          http://localhost:8580;
        proxy_next_upstream error timeout invalid_header http_500 http_502 http_503 http_504;
        proxy_redirect      http://localhost:8580 https://<FQDN>;
        proxy_buffering     off;
        proxy_set_header    Host                $http_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;
    }

    ssl_stapling on;
    ssl_stapling_verify on;

    resolver <local DNS server>;

    # additional headers for tandoor
    add_header Strict-Transport-Security "max-age=15552000; includeSubDomains";

    listen [::]:443 ssl; # managed by Certbot
    listen 443 ssl; # managed by Certbot
    ssl_certificate /etc/letsencrypt/live/<FQDN>/fullchain.pem; # managed by Certbot
    ssl_certificate_key /etc/letsencrypt/live/<FQDN>/privkey.pem; # managed by Certbot
    include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
    ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot

}

server {
    if ($host = <FQDN>) {
        return 301 https://$host$request_uri;
    } # managed by Certbot


    server_name  <FQDN>;
    listen [::]:80;
    listen 80;
    return 404; # managed by Certbot


}

When I inspect the tandoor_media volume on the host, I can see that images are being added, and both the web_recipes and nginx_recipes containers are able to see those files when I start a shell inside them.

As far as I can tell, I have followed the documentation closely and everything looks right to me. It is worth noting that when I try and access http://localhost:8580/ directly (i.e. bypassing the nginx reverse proxy), I get the exact same behaviour - I can log in, browse recipes, but cannot load any images.

I am running version 1.4.5 (latest at time of writing), and for good measure, here is the rest of the debug output from the System Information page (interestingly, it says that Media Serving is OK):

Gunicorn Media: False
Sqlite: False
Debug: False

SERVER_PROTOCOL:HTTP/1.0
REMOTE_ADDR:172.25.0.1
SERVER_PORT:8080

HTTP_HOST:<FQDN>
HTTP_X_REAL_IP:<CLIENT IP>
HTTP_X_FORWARDED_FOR:<CLIENT IP>
HTTP_X_FORWARDED_PROTO:https
HTTP_CONNECTION:close
HTTP_USER_AGENT:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:107.0) Gecko/20100101 Firefox/107.0
HTTP_ACCEPT:text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,*/*;q=0.8
HTTP_ACCEPT_LANGUAGE:en,en-US;q=0.5
HTTP_ACCEPT_ENCODING:gzip, deflate, br
HTTP_REFERER:<hidden>
HTTP_UPGRADE_INSECURE_REQUESTS:1
HTTP_DNT:1
HTTP_COOKIE:csrftoken=<hidden>
HTTP_SEC_FETCH_DEST:empty
HTTP_SEC_FETCH_MODE:same-origin
HTTP_SEC_FETCH_SITE:same-origin

wsgi.errors:<gunicorn.http.wsgi.WSGIErrorsWrapper object at 0x7f03fc187640>
wsgi.version:(1, 0)
wsgi.multithread:True
wsgi.multiprocess:True
wsgi.run_once:False
wsgi.file_wrapper:
wsgi.input_terminated:True
wsgi.input:<gunicorn.http.body.Body object at 0x7f03fc159900>
wsgi.url_scheme:http

Can anyone suggest what might be going wrong here?

@timdonovanuk
Copy link

I'm using the manual install instructions from here which seem to result in broken uploaded images. I had to do the following:

  • In .env have STATIC_URL=staticfiles/ but MEDIA_URL=media/.
  • In /etc/nginx/conf.d/recipes.conf the media files directive needing a trailing slash added, e.g. alias /var/www/recipes/mediafiles;

This was deduced working through various console errors. Perhaps it could be documented better, as many people appear to be having issues with this. I think also I saw setting debug=1 changes how the images are served, which might also be throwing people off. Cheers.

@tinju87
Copy link

tinju87 commented May 5, 2023

@timdonovanuk same about the docker-compose method...
Images are also broken if you do like:

Not working:
https://domain.com/media/recipes/b0b88398-ba8e-4ee8-85ad-7367b1f92a78_30.jpg

Working:
http://172.20.20.1:8081/media/recipes/b0b88398-ba8e-4ee8-85ad-7367b1f92a78_30.jpg

This is only concerning images everything else is working fine. (I had to change port because 80 is already used).

Edit: created #2459

@smilerz
Copy link
Collaborator

smilerz commented May 5, 2023

@timdonovanuk same about the docker-compose method... Images are also broken if you do like:

Not working: https://domain.com/media/recipes/b0b88398-ba8e-4ee8-85ad-7367b1f92a78_30.jpg

Working: http://172.20.20.1:8081/media/recipes/b0b88398-ba8e-4ee8-85ad-7367b1f92a78_30.jpg

This is only concerning images everything else is working fine. (I had to change port because 80 is already used).

Your issue isn't the nginx config in docker. please open a new issue and share your docker config and .env

@Grougalo
Copy link

Grougalo commented Apr 23, 2024

Hello,

+1 @timdonovanuk

I think also I saw setting debug=1 changes how the images are served, which might also be throwing people off. Cheers.

Just implemented it with docker-compose and nginx reverse-proxy on a raspberry pi.

=> With the .env debug=1 , I can load the images.
=> With the .env debug=0 , I cannot load any images with a 404 error.

If it can help in any case, let me know if logs or conf are needed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
documentation Improvements or additions to documentation setup issue possibly or definitely an issue with the user setup
Projects
None yet
Development

No branches or pull requests

10 participants