Setting Up Invoice Ninja on QNAP Container Station

Hi all,

Trying to set up Invoice Ninja on my QNAP NAS using Container Station which is using Docker.

I have the following YAML code to set up my application (the YAML file is based on the code in the docker-compose file on GitHub):

services:
  app:
    build:
      context: .
    image: invoiceninja/invoiceninja-debian:latest
    restart: unless-stopped
    env_file:
      - /share/Container/invoiceninja/.env
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Chicago
    volumes:
      # - ./php/php.ini:/usr/local/etc/php/conf.d/invoiceninja.ini:ro
      # - ./php/php-fpm.conf:/usr/local/etc/php-fpm.d/invoiceninja.conf:ro
      # - ./supervisor/supervisord.conf:/etc/supervisor/conf.d/supervisord.conf:ro
      - /share/Container/invoiceninja/public:/var/www/html/public
      - /share/Container/invoiceninja/storage:/var/www/html/storage
    ports:
      - "8012:8012"
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - ./nginx:/etc/nginx/conf.d:ro
      - /share/Container/invoiceninja/public:/var/www/html/public:ro
      - /share/Container/invoiceninja/storage:/var/www/html/storage:ro
    depends_on:
      app:
        condition: service_healthy

  mysql:
    image: mysql:8
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: ${DB_DATABASE}
      MYSQL_USER: ${DB_USERNAME}
      MYSQL_PASSWORD: ${DB_PASSWORD}
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASSWORD}
    volumes:
      - /share/Container/invoiceninja/mysql_data:/var/lib/mysql
    healthcheck:
      test:
        [
          "CMD",
          "mysqladmin",
          "ping",
          "-h",
          "localhost",
          "-u${MYSQL_USER}",
          "-p${MYSQL_PASSWORD}",
        ]

  redis:
    image: redis:alpine
    restart: unless-stopped
    volumes:
      - /share/Container/invoiceninja/redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]

And here is my .env file:

# IN application vars
APP_URL=http://localhost:8012
APP_KEY=base64:7some_key_code
APP_ENV=production
APP_DEBUG=true
REQUIRE_HTTPS=false
PHANTOMJS_PDF_GENERATION=false
PDF_GENERATOR=snappdf
TRUSTED_PROXIES='*'


CACHE_DRIVER=redis
QUEUE_CONNECTION=redis
SESSION_DRIVER=redis

REDIS_HOST=redis
REDIS_PASSWORD=null
REDIS_PORT=6379

FILESYSTEM_DISK=debian_docker

# DB connection
DB_HOST=mysql
DB_PORT=3306
DB_DATABASE=ninja
DB_USERNAME=ninja
DB_PASSWORD=*******
DB_ROOT_PASSWORD=********
DB_CONNECTION=mysql

# Create initial user
# Default to these values if empty
[email protected]
IN_PASSWORD=changeme!
# IN_USER_EMAIL=
# IN_PASSWORD=

# Mail options
MAIL_MAILER=log
MAIL_HOST=smtp.mailtrap.io
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_ADDRESS='[email protected]'
MAIL_FROM_NAME='Self Hosted User'

# MySQL
MYSQL_ROOT_PASSWORD=*****
MYSQL_USER=ninja
MYSQL_PASSWORD=*****
MYSQL_DATABASE=ninja

# GoCardless/Nordigen API key for banking integration
NORDIGEN_SECRET_ID=
NORDIGEN_SECRET_KEY=

IS_DOCKER=true
SCOUT_DRIVER=null
#SNAPPDF_CHROMIUM_PATH=/usr/bin/google-chrome-stable

Everything starts up fine, but I can’t access the webpage. I keep getting connection refused on port 8012. I’ve tried using http://192.168.0.3:8012 in the .env file instead of localhost as that is the IP address of the NAS.

I’m kinda stumped. It looks like it’s running, I just can’t figure out how to access it…

Thanks,

Jon

Hi,

I’m not sure but maybe the info from Gemini will help?

It looks like the main issue is a combination of how the containers are communicating and which port you are trying to access in your browser.

In your current setup, the Nginx container is the “front door” for the application, not the app container. Here are a few things to check and fix:

1. Port Mapping Conflict
In your YAML, the app service is mapping 8012:8012. However, the Invoice Ninja Debian image typically listens on port 8000 (for the built-in web server) or uses PHP-FPM. More importantly, your nginx service is already mapping 80:80.

  • The Fix: You should try to access the application via the Nginx port, not the app port. If you want to use port 8012 to access Invoice Ninja, change the nginx service ports to:
ports:
  - "8012:80"

Then, you can remove the ports section from the app service entirely, as Nginx communicates with it over the internal Docker network.

2. Update APP_URL
Since you are accessing this from a different device on your network, localhost won’t work because the browser will look for the app on your computer, not the NAS.

  • Update your .env to: APP_URL=http://192.168.0.3:8012

3. Check the Nginx Config
You have a volume mapping for Nginx: - ./nginx:/etc/nginx/conf.d:ro.

  • Ensure that the configuration file inside that folder is correctly pointing to the app service on the internal port (usually 9000 if using FPM, or 8000 if using the web-server variant).
  • If you are using the standard Invoice Ninja docker-compose example, ensure the fastcgi_pass is set to app:9000.

4. Permissions on QNAP
QNAP can be picky with folder permissions. Ensure the user with PUID=1000 has full read/write access to the /share/Container/invoiceninja/ directory. If the app can’t write to the storage or public folders, it may fail to initialize.

Summary of steps:

  1. Change nginx ports to "8012:80".
  2. Remove ports from the app service.
  3. Update APP_URL to your NAS IP with port 8012.
  4. Restart the stack.

Thank you for your response. Unfortunately, after making those changes, I am still not having any luck.

One thing I have tried to change but it doesn’t seem like it’s making a difference is to change the volume making for Nginx. It had been - ./nginx:/etc/nginx/conf.d:ro to /share/Container/invoiceninja/nginx:/etc/nginx/conf.d:ro this way, I would have access to the configuration file to check the internal port. But that directory is never being created. So I am wondering what I am doing wrong. I completely removed the app and rebuilt it. Same issue. Can’t connect…

Maybe you can try asking for help on a QNAP forum?

Yeah, I’m very involved there. Haven’t asked yet but I will. Thought I would come here first…

Hey all,

I’m following up on this instead of creating a new thread.

QNAP devices don’t always work really well with .env files, so I pulled all of the settings in the .env files into my compose file. Code is below.

I get the container to start up and all - everything appears to be working. Problem is I can’t access via my web browser no matter what I try. Nothing works. So I am stuck. Would really like to get some help as it’s got to be something in the YAML file…

In this latest one I’ve tried using a fixed IP set up on my system. Nope. Doesn’t work. Not sure where things are going south…

services:
  app:
    build:
      context: .
    image: invoiceninja/invoiceninja-debian:latest
    restart: unless-stopped
    user: "0:0"
 #   env_file:
 #     - /share/ZFS29_DATA/Container/invoiceninja/.env
    environment:
      - PUID=1000
      - PGID=1000
      - TZ=America/Chicago
# IN application vars
      - APP_URL=http://192.168.1.22
      - APP_KEY=base64:xxxxxxxxxxxxx
      - APP_ENV=production
      - APP_DEBUG=true
      - REQUIRE_HTTPS=false
      - PHANTOMJS_PDF_GENERATION=false
      - PDF_GENERATOR=snappdf
      - TRUSTED_PROXIES='*'
      - CACHE_DRIVER=redis
      - QUEUE_CONNECTION=redis
      - SESSION_DRIVER=redis
      - REDIS_HOST=redis
      - REDIS_PASSWORD=null
      - REDIS_PORT=6379
      - FILESYSTEM_DISK=debian_docker
# DB connection
      - DB_HOST=mysql
      - DB_PORT=3306
      - DB_DATABASE=xxxxx
      - DB_USERNAME=xxxxx
      - DB_PASSWORD=xxxx
      - DB_ROOT_PASSWORD=xxxxxxx
      - DB_CONNECTION=mysql
# Create initial user
# Default to these values if empty
      - [email protected]
      - IN_PASSWORD=changeme!
# IN_USER_EMAIL=
# IN_PASSWORD=
# Mail options
      - MAIL_MAILER=log
      - MAIL_HOST=smtp.mailtrap.io
      - MAIL_PORT=2525
      - MAIL_USERNAME=null
      - MAIL_PASSWORD=null
      - MAIL_ENCRYPTION=null
      - MAIL_FROM_ADDRESS='[email protected]'
      - MAIL_FROM_NAME='Self Hosted User'
# MySQL
      - MYSQL_ROOT_PASSWORD=ninjaAdm1nPassword
      - MYSQL_USER=ninja
      - MYSQL_PASSWORD=ninja
      - MYSQL_DATABASE=ninja
# GoCardless/Nordigen API key for banking integration
      - NORDIGEN_SECRET_ID=
      - NORDIGEN_SECRET_KEY=
      - IS_DOCKER=true
      - SCOUT_DRIVER=null
#SNAPPDF_CHROMIUM_PATH=/usr/bin/google-chrome-stable
#    ports:
#     - 8012:8012
    volumes:
      # - ./php/php.ini:/usr/local/etc/php/conf.d/invoiceninja.ini:ro
      # - ./php/php-fpm.conf:/usr/local/etc/php-fpm.d/invoiceninja.conf:ro
      # - ./supervisor/supervisord.conf:/etc/supervisor/conf.d/supervisord.conf:ro
      - /share/ZFS29_DATA/Container/invoiceninja/public:/var/www/html/public
      - /share/ZFS29_DATA/Container/invoiceninja/storage:/var/www/html/storage
    depends_on:
      mysql:
        condition: service_healthy
      redis:
        condition: service_healthy

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "80:80"
    volumes:
      - /share/ZFS29_DATA/Container/invoiceninja/nginx:/etc/nginx/conf.d:ro
      - /share/ZFS29_DATA/Container/invoiceninja/public:/var/www/html/public:ro
      - /share/ZFS29_DATA/Container/invoiceninja/storage:/var/www/html/storage:ro
    depends_on:
      app:
        condition: service_healthy

  mysql:
    image: mysql:8
    restart: unless-stopped
    environment:
      MYSQL_DATABASE: xxxxxx
      MYSQL_USER: xxxxxx
      MYSQL_PASSWORD: xxxxxx
      MYSQL_ROOT_PASSWORD: xxxxxx
    volumes:
      - /share/ZFS29_DATA/Container/invoiceninja/mysql_data:/var/lib/mysql
    healthcheck:
      test:
        [
          "CMD",
          "mysqladmin",
          "ping",
          "-h",
          "localhost",
          "-u${MYSQL_USER}",
          "-p${MYSQL_PASSWORD}",
        ]

  redis:
    image: redis:alpine
    restart: unless-stopped
    volumes:
      - /share/ZFS29_DATA/Container/invoiceninja/redis_data:/data
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]

I’m not sure, maybe this info from AI will help…

Based on the YAML you provided, there are three main culprits likely preventing you from seeing the login screen.

1. The “Healthcheck” Deadlock

This is the most likely reason. Your nginx service has this condition:
condition: service_healthy for the app service.

However, your app service does not have a healthcheck defined. In many Docker versions, if a service is waiting for another to be “healthy” but no healthcheck exists, the dependent service (Nginx) will stay in a paused state or fail to start because the “healthy” status is never reported.

The Fix:
Change the dependency in the nginx service or add a simple healthcheck to the app service.

Quickest Change:

  nginx:
    ...
    depends_on:
      app:
        condition: service_started # Change from service_healthy

2. Port 80 Conflict on QNAP

By default, QNAP’s QTS/QuTS hero management interface often grabs Port 80 and 443 for itself. If Nginx is trying to bind to "80:80", it might be failing silently or the QNAP web admin is intercepting the traffic.

The Fix:
Try mapping a custom external port to Nginx.

  nginx:
    image: nginx:alpine
    ports:
      - "8080:80" # Try accessing via http://192.168.1.22:8080

3. The Nginx Configuration File

You are mapping /share/ZFS29_DATA/Container/invoiceninja/nginx to /etc/nginx/conf.d.

  • Does that folder contain a .conf file? * Does that file point to the right place?

Since you are using the app service (which is likely PHP-FPM in that Debian image), your Nginx config file must point to the app service on port 9000. It should look something like this inside your .conf file:

location ~ \.php$ {
    fastcgi_pass app:9000; # 'app' matches your service name
    ...
}

If Nginx doesn’t find a valid config in that volume, it might start but won’t know how to talk to Invoice Ninja.


Recommended “Clean” Compose Structure

Here is a slightly refined version of your services to test these fixes:

services:
  app:
    image: invoiceninja/invoiceninja-debian:latest
    restart: unless-stopped
    user: "0:0"
    environment:
      - PUID=1000
      - PGID=1000
      - APP_URL=http://192.168.1.22:8080 # Match the port you choose
      - APP_KEY=base64:xxxxxxxxxxxxx
      - APP_DEBUG=true
      - DB_HOST=mysql
      - DB_DATABASE=ninja
      - DB_USERNAME=ninja
      - DB_PASSWORD=ninja
      # ... (rest of your env vars)
    volumes:
      - /share/ZFS29_DATA/Container/invoiceninja/public:/var/www/html/public
      - /share/ZFS29_DATA/Container/invoiceninja/storage:/var/www/html/storage
    depends_on:
      mysql:
        condition: service_healthy

  nginx:
    image: nginx:alpine
    restart: unless-stopped
    ports:
      - "8080:80" # Changed to avoid QNAP system conflict
    volumes:
      - /share/ZFS29_DATA/Container/invoiceninja/nginx:/etc/nginx/conf.d:ro
      - /share/ZFS29_DATA/Container/invoiceninja/public:/var/www/html/public:ro
    depends_on:
      - app # Simplified dependency

  # ... (mysql and redis remain the same)

A Quick Troubleshooting Tip

If it still won’t load, SSH into your QNAP and run this command in the folder where your docker-compose.yml is:

docker compose logs nginx

If you see an error like bind: address already in use, it’s definitely a port conflict with the QNAP interface.

Would you like me to provide a standard Nginx configuration file template that works specifically for Invoice Ninja?

Thank you for your response.

A few things:

First of all, I am somewhat confused why the example YAML file at
dockerfiles/debian/docker-compose.yml at debian · invoiceninja/dockerfiles · GitHub

does not work correctly. Who maintains this?

Yes, I have modified things, but items you point out like the health check are part of that.

OK. I have fixed this but it did not make a difference.

This should not be a problem as I am using a new static IP assigned to the Container. The problem is, which container gets the IP address? Is that the InvoiceNinja container or the Nginx container?

Maybe this is part of my issue. As I am looking at this, I am mapping a folder to a file. There is nothing in that folder. I have not created an nginx configuration file. Is what you show below all that is needed? Maybe I need to go back to the default mapping that is shown in the original YAML file? Problem is I can’t easily edit or get to that file the way QNAP does things.

Yep, I am thinking this might be the problem.

Yes please!

The response was generated using Gemini, to ask deeper questions I suggest asking an AI model yourself.

Hi, I have used the compose.yaml file in GitHub and set up 5.13.14 on my local machine. It just work flawlessly. The Healthcheck is not a problem.

From your conversation I suppose you don’t really know the mechanism of Docker and it is just very difficult to troubleshoot by asking AI.

I suggest you try to set it up on local machine first. Once you can connect to invoiceninja in your local browser. At least you know it is working. Then, you can start to deploy on your NAS.

So, please don’t blame the maintainer. The compose.yaml just work perfect.

Well, that’s good to know.

I don’t know Docker all that well. And the QNAP does add a few quirks to it. But given some of the features in InvoiceNinja like the customer portal and things like that, I’m really considering putting it on a VPS. If I could get InvoiceNinja working in the container on the NAS, then I would consider opening that port to the internet. But right now, I have it running under Apache on the NAS and I don’t consider the web server on the NAS to be secure enough to allow me to open it to the internet. So lots to consider.

I’m pretty much locked into my current invoicing provider until the end of the year, so I have some time before I need to make any permanent changes…

It is fine as long as it is working. Don’t bother using Docker unless you are a IT professional and love to fix things. I find that it is working on my local computer does not mean it will work on QNAP NAS. Deploying on NAS is a different issue actually. Just use it if it works!

Regarding the safety of Apache, I will not say it is not safe if you update the app and update your NAS firmware if security issue exists. But the nginx web server in Docker is more performant indeed.

About open port, if you have a domain name at your hand, you can use Cloudflare reverse tunnel. No need to open port to access invoiceninja.