inventree / InvenTree

Open Source Inventory Management System
https://docs.inventree.org
MIT License
4.33k stars 784 forks source link

Run InvenTree with Caddy behind NGINX Reverse Proxy #6720

Closed x4FF3 closed 8 months ago

x4FF3 commented 8 months ago

Deployment Method

Describe the problem*

Hi,

i run multiple services in my homelab. Often i'll make them externally reachable via an NGINX reverse proxy. I tried the same thing with the docker-compose setup, but i have no luck getting it to work. I can reach InvenTree without problem inside my network, but when trying to reach via the reverse proxy, i just get an empty response from Caddy.

Steps to Reproduce

  1. Change URL to http://$URL to skip TLS generation
  2. Set both X-Forwarded to true

Relevant log output

-
SchrodingersGat commented 8 months ago

This is outside the scope of InvenTree itself, but hopefully we can work something out.

Is your internal caddy proxy listening on the correct host? You may have to set the Caddyfile to listen to the internal host or IP?

x4FF3 commented 8 months ago

Here are my currently used InvenTree files. I have no problem to reach InvenTree via http://server.fritz.box:8108. The NGINX reverse proxy points to this URL as well. But i cannot reach it via the external domain

# Caddyfile for Inventree
# The following environment variables may be used:
# - INVENTREE_SITE_URL: The upstream URL of the Inventree site (default: inventree.localhost)
# - INVENTREE_SERVER: The internal URL of the Inventree container (default: http://inventree-server:8000)

(log_common) {
        log {
                output file /var/log/caddy/{args[0]}.access.log
        }
}

(cors-headers) {
        header Allow GET,HEAD,OPTIONS
        header Access-Control-Allow-Origin *
        header Access-Control-Allow-Methods GET,HEAD,OPTIONS
        header Access-Control-Allow-Headers Authorization,Content-Type,User-Agent

        @cors_preflight{args[0]} method OPTIONS

        handle @cors_preflight{args[0]} {
                respond "" 204
        }
}

# Change the host to your domain (this will serve at inventree.localhost)
http://server.fritz.box {
        import log_common inventree

        encode gzip

        request_body {
                max_size 100MB
        }

        handle_path /static/* {
                import cors-headers static

                root * /var/www/static
                file_server
        }

        handle_path /media/* {
                import cors-headers media

                root * /var/www/media
                file_server

                forward_auth "http://inventree-server:8000" {
                        uri /auth/
                }
        }
        reverse_proxy "http://inventree-server:8000"
}

.env

# InvenTree environment variables for docker compose deployment

# Specify the location of the external data volume
# By default, placed in local directory 'inventree-data'
INVENTREE_EXT_VOLUME=./inventree-data

# Ensure debug is false for a production setup
INVENTREE_DEBUG=False
INVENTREE_LOG_LEVEL=WARNING

# InvenTree admin account details
# Un-comment (and complete) these lines to auto-create an admin acount
INVENTREE_ADMIN_USER=admin
INVENTREE_ADMIN_PASSWORD=thisisnotsecure
INVENTREE_ADMIN_EMAIL=david@fuellgraf.eu

# Database configuration options
INVENTREE_DB_ENGINE=postgresql
INVENTREE_DB_NAME=inventree
INVENTREE_DB_HOST=inventree-db
INVENTREE_DB_PORT=5432

# Database credentials - These should be changed from the default values!
INVENTREE_DB_USER=pguser
INVENTREE_DB_PASSWORD=pgpassword

# Redis cache setup (disabled by default)
# Un-comment the following lines to enable Redis cache
# Note that you will also have to run docker-compose with the --profile redis command
# Refer to settings.py for other cache options
#INVENTREE_CACHE_HOST=inventree-cache
#INVENTREE_CACHE_PORT=6379

# Options for gunicorn server
INVENTREE_GUNICORN_TIMEOUT=90

# Enable custom plugins?
INVENTREE_PLUGINS_ENABLED=True

# Run migrations automatically?
INVENTREE_AUTO_UPDATE=True

# Image tag that should be used
INVENTREE_TAG=stable

# Site URL - update this to match your host
INVENTREE_SITE_URL="http://server.fritz.box:8108"

COMPOSE_PROJECT_NAME=inventree

INVENTREE_CORS_ORIGIN_WHITELIST="http://server.fritz.box:8108,https://inventar.domain.tld"
INVENTREE_TRUSTED_ORIGINS="http://server.fritz.box:8108,https://inventar.domain.tld"
INVENTREE_USE_X_FORWARDED_HOST=True
INVENTREE_USE_X_FORWARDED_PORT=True
INVENTREE_CORS_ORIGIN_ALLOW_ALL=True

docker-compose


services:
    # Database service
    # Use PostgreSQL as the database backend
    inventree-db:
        image: postgres:13
        container_name: inventree-db
        expose:
            - ${INVENTREE_DB_PORT:-5432}/tcp
        environment:
            - PGDATA=/var/lib/postgresql/data/pgdb
            - POSTGRES_USER=${INVENTREE_DB_USER:?You must provide the 'INVENTREE_DB_USER' variable in the .env file}
            - POSTGRES_PASSWORD=${INVENTREE_DB_PASSWORD:?You must provide the 'INVENTREE_DB_PASSWORD' variable in the .env file}
            - POSTGRES_DB=${INVENTREE_DB_NAME:?You must provide the 'INVENTREE_DB_NAME' variable in the .env file}
        volumes:
            # Map 'data' volume such that postgres database is stored externally
            - ${INVENTREE_EXT_VOLUME:?You must specify the 'INVENTREE_EXT_VOLUME' variable in the .env file!}:/var/lib/postgresql/data/:z
        restart: unless-stopped

    # redis acts as database cache manager
    # only runs under the "redis" profile : https://docs.docker.com/compose/profiles/
    inventree-cache:
        image: redis:7.0
        container_name: inventree-cache
        depends_on:
            - inventree-db
        profiles:
            - redis
        env_file:
            - .env
        expose:
            - ${INVENTREE_CACHE_PORT:-6379}
        restart: always

    # InvenTree web server service
    # Uses gunicorn as the web server
    inventree-server:
        # If you wish to specify a particular InvenTree version, do so here
        image: inventree/inventree:stable
        container_name: inventree-server
        # Only change this port if you understand the stack.
        expose:
            - 8000
        depends_on:
            - inventree-db
        env_file:
            - .env
        volumes:
            # Data volume must map to /home/inventree/data
            - ${INVENTREE_EXT_VOLUME}:/home/inventree/data:z
        restart: unless-stopped

    # Background worker process handles long-running or periodic tasks
    inventree-worker:
        # If you wish to specify a particular InvenTree version, do so here
        image: inventree/inventree:${INVENTREE_TAG:-stable}
        container_name: inventree-worker
        command: invoke worker
        depends_on:
            - inventree-server
        env_file:
            - .env
        volumes:
            # Data volume must map to /home/inventree/data
            - ${INVENTREE_EXT_VOLUME}:/home/inventree/data:z
        restart: unless-stopped

    # caddy acts as reverse proxy and static file server
    # https://hub.docker.com/_/caddy
    inventree-proxy:
        container_name: inventree-proxy
        image: caddy:alpine
        restart: always
        depends_on:
            - inventree-server
        ports:
            - 8108:80
        env_file:
            - .env
        volumes:
            - ./Caddyfile:/etc/caddy/Caddyfile:ro
            - ${INVENTREE_EXT_VOLUME}/static:/var/www/static:z
            - ${INVENTREE_EXT_VOLUME}/media:/var/www/media:z
            - ${INVENTREE_EXT_VOLUME}:/var/log:z
            - ${INVENTREE_EXT_VOLUME}:/data:z
            - ${INVENTREE_EXT_VOLUME}:/config:z

NGINX

server {
  set $forward_scheme http;
  set $server         "server.fritz.box";
  set $port           8108;

  listen 80;
  listen [::]:80;

  listen 443 ssl http2;
  listen [::]:443 ssl http2;

  server_name inventar.domain.tld;

  # Let's Encrypt SSL
  include conf.d/include/letsencrypt-acme-challenge.conf;
  include conf.d/include/ssl-ciphers.conf;
  ssl_certificate /etc/letsencrypt/live/npm-3/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/npm-3/privkey.pem;

  # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
  add_header Strict-Transport-Security $hsts_header always;

    # Force SSL
    include conf.d/include/force-ssl.conf;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_http_version 1.1;

    access_log /data/logs/proxy-host-3_access.log proxy;
    error_log /data/logs/proxy-host-3_error.log warn;

    proxy_set_header X-Real-IP $remote_addr;
    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
    proxy_set_header Host $http_host;
    proxy_set_header X-NginX-Proxy true;

  location / {

  # HSTS (ngx_http_headers_module is required) (63072000 seconds = 2 years)
  add_header Strict-Transport-Security $hsts_header always;

    proxy_set_header Upgrade $http_upgrade;
    proxy_set_header Connection $http_connection;
    proxy_http_version 1.1;

    add_header       X-Served-By $host;
    proxy_set_header Host $host;
    proxy_set_header X-Forwarded-Scheme $scheme;
    proxy_set_header X-Forwarded-Proto  $scheme;
    proxy_set_header X-Forwarded-For    $proxy_add_x_forwarded_for;
    proxy_set_header X-Real-IP          $remote_addr;
    proxy_pass       $forward_scheme://$server:$port$request_uri;
  }

  # Custom
  include /data/nginx/custom/server_proxy[.]conf;
}
SchrodingersGat commented 8 months ago

Does adding https://inventar.domain.tld as a potential host path to your Caddyfile help here?

e..g

...
# Change the host to your domain (this will serve at inventree.localhost)
https://inventar.domain.tld http://server.fritz.box {
        import log_common inventree

        encode gzip

        request_body {
                max_size 100MB
        }

        handle_path /static/* {
                import cors-headers static
...
x4FF3 commented 8 months ago

Does adding https://inventar.domain.tld as a potential host path to your Caddyfile help here?

e..g

...
# Change the host to your domain (this will serve at inventree.localhost)
https://inventar.domain.tld http://server.fritz.box {
        import log_common inventree

        encode gzip

        request_body {
                max_size 100MB
        }

        handle_path /static/* {
                import cors-headers static
...

i added it with http://inventar.domain.tld to skip tls generation and it works now :) thanks!

SchrodingersGat commented 8 months ago

@x4FF3 glad to hear you got it working.