Images and files not downloadable in the docker image

Dear all,

I am having problems with the docker image of ERPNext.

I installed the images, since the server will be behind a reverse proxy taking care of ssh, etc, I used the alternative docker-compose.yml mentioned here by @revant_one:

The host and site name is erp.localhost, which resolves to 127.0.0.1. On the machine itself (without reverse proxy), ERPNext is accessible via erp.localhost:8080 and works fine, with the exception that files are not downloadable, and pictures don’t show up in preview, invoices, etc.

The error message I get when trying to download pictures is translated “graphics “http:// erp.localhost:8080/files/filename.jpg” can not be displayed because it contains errors”. Attachments are empty.

Any ideas what is happening?

best,
Thomas

How can I replicate your setup locally? Can you share your docker compose files?

Of course, and thank you for your engagement !

version: “3”

services:

[ traefik section commented out]

erpnext-nginx:
image: frappe/erpnext-nginx:${ERPNEXT_VERSION}
restart: on-failure
environment:
- FRAPPE_PY=erpnext-python
- FRAPPE_PY_PORT=8000
- FRAPPE_SOCKETIO=frappe-socketio
- SOCKETIO_PORT=9000
labels:
- “traefik.enable=true”
- “traefik.http.routers.erpnext-nginx.rule=Host(${SITES})”
- “${ENTRYPOINT_LABEL}”
- “${CERT_RESOLVER_LABEL}”
- “traefik.http.services.erpnext-nginx.loadbalancer.server.port=80”
volumes:
- sites-vol:/var/www/html/sites:rw
- assets-vol:/assets:rw
ports:
- 8080:8080

erpnext-python:
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
restart: on-failure
environment:
- MARIADB_HOST=${MARIADB_HOST}
- REDIS_CACHE=redis-cache:6379
- REDIS_QUEUE=redis-queue:6379
- REDIS_SOCKETIO=redis-socketio:6379
- SOCKETIO_PORT=9000
- AUTO_MIGRATE=1
volumes:
- sites-vol:/home/frappe/frappe-bench/sites:rw
- assets-vol:/home/frappe/frappe-bench/sites/assets:rw

frappe-socketio:
image: frappe/frappe-socketio:${FRAPPE_VERSION}
restart: on-failure
depends_on:
- redis-socketio
volumes:
- sites-vol:/home/frappe/frappe-bench/sites:rw

erpnext-worker-default:
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
restart: on-failure
command: worker
depends_on:
- redis-queue
- redis-cache
volumes:
- sites-vol:/home/frappe/frappe-bench/sites:rw

erpnext-worker-short:
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
restart: on-failure
command: worker
environment:
- WORKER_TYPE=short
depends_on:
- redis-queue
- redis-cache
volumes:
- sites-vol:/home/frappe/frappe-bench/sites:rw

erpnext-worker-long:
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
restart: on-failure
command: worker
environment:
- WORKER_TYPE=long
depends_on:
- redis-queue
- redis-cache
volumes:
- sites-vol:/home/frappe/frappe-bench/sites:rw

erpnext-schedule:
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
restart: on-failure
command: schedule
depends_on:
- redis-queue
- redis-cache
volumes:
- sites-vol:/home/frappe/frappe-bench/sites:rw

redis-cache:
image: redis:latest
restart: on-failure
volumes:
- redis-cache-vol:/data

redis-queue:
image: redis:latest
restart: on-failure
volumes:
- redis-queue-vol:/data

redis-socketio:
image: redis:latest
restart: on-failure
volumes:
- redis-socketio-vol:/data

mariadb:
image: mariadb:10.3
restart: on-failure
command:
- --character-set-server=utf8mb4
- --collation-server=utf8mb4_unicode_ci
- --skip-character-set-client-handshake
environment:
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- MYSQL_INITDB_SKIP_TZINFO=1
volumes:
- ./installation/frappe-mariadb.cnf:/etc/mysql/conf.d/frappe.cnf
- mariadb-vol:/var/lib/mysql

site-creator:
image: frappe/erpnext-worker:${ERPNEXT_VERSION}
restart: “no”
command: new
depends_on:
- erpnext-python
environment:
- SITE_NAME=${SITE_NAME}
- DB_ROOT_USER=${DB_ROOT_USER}
- MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD}
- ADMIN_PASSWORD=${ADMIN_PASSWORD}
- INSTALL_APPS=${INSTALL_APPS}
volumes:
- sites-vol:/home/frappe/frappe-bench/sites:rw

volumes:
mariadb-vol:
redis-cache-vol:
redis-queue-vol:
redis-socketio-vol:
assets-vol:
sites-vol:
cert-vol:

and .env

LETSENCRYPT_EMAIL=
ERPNEXT_VERSION=v13.17.0
FRAPPE_VERSION=v13.17.1
MARIADB_HOST=mariadb
MYSQL_ROOT_PASSWORD=admin
SITE_NAME=erp.localhost
SITES=erp.localhost
DB_ROOT_USER=root
ADMIN_PASSWORD=admin
INSTALL_APPS=erpnext
ENTRYPOINT_LABEL=traefik.http.routers.erpnext-nginx.entrypoints=websecure
CERT_RESOLVER_LABEL=traefik.http.routers.erpnext-nginx.tls.certresolver=myresolver
HTTPS_REDIRECT_RULE_LABEL=traefik.http.routers.http-catchall.rule=hostregexp({host:.+})
HTTPS_REDIRECT_ENTRYPOINT_LABEL=traefik.http.routers.http-catchall.entrypoints=web
HTTPS_REDIRECT_MIDDLEWARE_LABEL=traefik.http.routers.http-catchall.middlewares=redirect-to-https
HTTPS_USE_REDIRECT_MIDDLEWARE_LABEL=traefik.http.middlewares.redirect-to-https.redirectscheme.scheme=https
SKIP_NGINX_TEMPLATE_GENERATION=1
WORKER_CLASS=gthread

Thanks a lot !

I made following change to /etc/nginx/conf.d/default.conf

server {
    ...
    location / {
        ...
        location ~ ^/files/.*.(htm|html|svg|xml) {
                add_header Content-disposition "attachment";
                try_files /sites/$host/public/$uri @webserver;
        }

        try_files /sites/$host/public/$uri @webserver;
    }
    ...
}

Full default.conf
upstream frappe-server {
    server erpnext-python:8000 fail_timeout=0;
}

upstream socketio-server {
    server frappe-socketio:9000 fail_timeout=0;
}

# Parse the X-Forwarded-Proto header - if set - defaulting to $scheme.
map $http_x_forwarded_proto $proxy_x_forwarded_proto {
    default $scheme;
    https https;
}

server {
    listen 8080;
    server_name $http_host;
    root /var/www/html;

    add_header X-Frame-Options "SAMEORIGIN";
    add_header Strict-Transport-Security "max-age=63072000; includeSubDomains; preload";
    add_header X-Content-Type-Options nosniff;
    add_header X-XSS-Protection "1; mode=block";

    # Define 127.0.0.1 as our trusted upstream address, so we will be using
    # its X-Forwarded-For address as our remote address
    set_real_ip_from 127.0.0.1;
    real_ip_header X-Forwarded-For;
    real_ip_recursive off;

    location /assets {
        try_files $uri =404;
    }

    location ~ ^/protected/(.*) {
        internal;
        try_files /sites/$http_host/$1 =404;
    }

    location /socket.io {
        proxy_http_version 1.1;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "upgrade";
        proxy_set_header Origin $proxy_x_forwarded_proto://$http_host;
        proxy_set_header X-Frappe-Site-Name $host;
        proxy_set_header Host $http_host;

        proxy_pass http://socketio-server;
    }

    location / {
        rewrite ^(.+)/$ $1 permanent;
        rewrite ^(.+)/index\.html$ $1 permanent;
        rewrite ^(.+)\.html$ $1 permanent;

        location ~ ^/files/.*.(htm|html|svg|xml) {
                add_header Content-disposition "attachment";
                try_files /sites/$host/public/$uri @webserver;
        }

        try_files /sites/$host/public/$uri @webserver;
    }

    location @webserver {
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $proxy_x_forwarded_proto;
        proxy_set_header X-Frappe-Site-Name $host;
        proxy_set_header Host $http_host;
        proxy_set_header X-Use-X-Accel-Redirect True;
        proxy_read_timeout 120;
        proxy_redirect off;

        proxy_pass  http://frappe-server;
    }

    # error pages
    error_page 502 /502.html;
    location /502.html {
        root /var/www/templates;
        internal;
    }

    # optimizations
    sendfile on;
    keepalive_timeout 15;
    client_max_body_size 50m;
    client_body_buffer_size 16K;
    client_header_buffer_size 1k;

    # enable gzip compression
    # based on https://mattstauffer.co/blog/enabling-gzip-on-nginx-servers-including-laravel-forge
    gzip on;
    gzip_http_version 1.1;
    gzip_comp_level 5;
    gzip_min_length 256;
    gzip_proxied any;
    gzip_vary on;
    gzip_types
        application/atom+xml
        application/javascript
        application/json
        application/rss+xml
        application/vnd.ms-fontobject
        application/x-font-ttf
        application/font-woff
        application/x-web-app-manifest+json
        application/xhtml+xml
        application/xml
        font/opentype
        image/svg+xml
        image/x-icon
        text/css
        text/plain
        text/x-component;
        # text/html is always compressed by HttpGzipModule
}

For that to work, I set SKIP_NGINX_TEMPLATE_GENERATION=1 env variable for erpnext-nginx container. and mounted the custom_nginx.conf inside container.

services:
  erpnext-nginx:
    ...
    environment:
      ...
      - SKIP_NGINX_TEMPLATE_GENERATION=1
    volumes:
     ...
      - ./custom_nginx.conf:/etc/nginx/conf.d/default.conf
  ...

https://github.com/frappe/frappe_docker/issues/629

Hi revamt_one,

works perfectly !!!

Thanks a lot !
best,
Thomas