Deploying Invoice Ninja – An Open-Source Invoicing Application

php dev.to

Invoice Ninja is an open-source, Laravel-based invoicing app for sending invoices, tracking payments, expenses, and time, a free alternative to FreshBooks or QuickBooks. This guide covers two install paths on Ubuntu 24.04: a native LEMP-stack release install, and a Docker Compose stack with Nginx, MariaDB, and Redis.

Prerequisites: an Ubuntu 24.04 server, non-root sudo user, a domain A record (e.g. invoiceninja.example.com).


Common Setup

$sudo apt update
$sudo ufw allow http && sudo ufw allow https
Enter fullscreen mode Exit fullscreen mode

Pick one of the two install paths below.


Option A: Install from the Release File

Gets you the latest features and runs on a standard LEMP stack. Requires PHP 8.3+.

Install Dependencies

$sudo apt install nginx mysql-server php -y
$php -v
$sudo apt install php8.3-bcmath php8.3-gmp php8.3-fileinfo \
    php8.3-gd php8.3-mbstring php8.3-pdo php8.3-xml php8.3-cli \
    php8.3-curl php8.3-zip php8.3-gmp php8.3-mysql php8.3-fpm -y
$sudo systemctl enable nginx
$sudo systemctl start nginx
$sudo systemctl status nginx
Enter fullscreen mode Exit fullscreen mode

Create the Database

$sudo mysql -u root
Enter fullscreen mode Exit fullscreen mode
mysql> CREATE DATABASE invoiceninjadb;
mysql> CREATE USER 'invoiceninja-admin'@'localhost' IDENTIFIED BY 'secure-password';
mysql> GRANT ALL PRIVILEGES ON invoiceninjadb.* TO 'invoiceninja-admin'@'localhost';
mysql> FLUSH PRIVILEGES;
mysql> EXIT;
Enter fullscreen mode Exit fullscreen mode

Download and Extract

Check the releases page for the current version:

$sudo mkdir -p /var/www/invoiceninja
$cd /var/www/invoiceninja
$sudo wget https://github.com/invoiceninja/invoiceninja/releases/download/v5.11.72/invoiceninja.tar.gz
$sudo tar -xvf invoiceninja.tar.gz
$sudo rm invoiceninja.tar.gz
$sudo cp .env.example .env
$sudo chown -R www-data:www-data /var/www/invoiceninja
Enter fullscreen mode Exit fullscreen mode

Set Up the Scheduler

$sudo -u www-data crontab -e
Enter fullscreen mode Exit fullscreen mode
* * * * * php8.3 /var/www/invoiceninja/artisan schedule:run >> /dev/null 2>&1
Enter fullscreen mode Exit fullscreen mode
$sudo -u www-data crontab -l
Enter fullscreen mode Exit fullscreen mode

Configure Nginx

$sudo nano /etc/nginx/sites-available/invoiceninja.conf
Enter fullscreen mode Exit fullscreen mode
server {
    listen 80;
    listen [::]:80;
    server_name invoiceninja.example.com;

    root /var/www/invoiceninja/public;
    index index.php index.html index.htm;

    client_max_body_size 20M;
    charset utf-8;

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

    gzip on;
    gzip_types application/javascript application/x-javascript text/javascript text/plain application/xml application/json;
    gzip_proxied no-cache no-store private expired auth;
    gzip_min_length 1000;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    if (!-e $request_filename) {
        rewrite ^(.+)$ /index.php?q= last;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass unix:/run/php/php8.3-fpm.sock;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors off;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
    }
}
Enter fullscreen mode Exit fullscreen mode
$sudo rm /etc/nginx/sites-enabled/default
$sudo ln -s /etc/nginx/sites-available/invoiceninja.conf /etc/nginx/sites-enabled/invoiceninja.conf
$sudo nginx -t
$sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

TLS via Let's Encrypt

$sudo apt install python3-certbot-nginx
$sudo certbot --nginx -d invoiceninja.example.com -m admin@example.com --agree-tos
$sudo systemctl restart nginx
Enter fullscreen mode Exit fullscreen mode

Run the Web Setup

Visit https://invoiceninja.example.com/setup:

  1. Enter your domain in URL, keep HTTPS Require enabled.
  2. Under Database Connection: driver MySQL, host localhost, port 3306, and the DB credentials created above. Click Test connection.
  3. Fill in admin user details, accept terms, click Submit.

Option B: Install with Docker Compose

Runs Nginx, MariaDB, Redis, and the app as containers — no manual package installs.

$sudo apt install docker.io docker-compose certbot python3-certbot-nginx -y
$sudo apt purge nginx -y
$cd
$mkdir invoiceninja && cd invoiceninja
$mkdir -p storage config public
$sudo chmod -R 777 storage public
Enter fullscreen mode Exit fullscreen mode

Generate an app key:

$sudo docker run --rm -it invoiceninja/invoiceninja php artisan key:generate --show
Enter fullscreen mode Exit fullscreen mode

Create docker-compose.yml:

$nano docker-compose.yml
Enter fullscreen mode Exit fullscreen mode
version: '3.7'

services:
  nginx:
    image: nginx
    restart: unless-stopped
    volumes:
      - ./config/in-vhost.conf:/etc/nginx/conf.d/in-vhost.conf:ro
      - ./public:/var/www/app/public:ro
      - /etc/letsencrypt:/etc/letsencrypt:ro
    depends_on:
      - app
    ports:
      - "80:80"
      - "443:443"
    healthcheck:
      test: curl -f http://localhost:80/ || exit 1
    networks:
      - invoice_ninja_network

  db:
    image: mariadb:10.4
    restart: unless-stopped
    volumes:
      - ./data:/var/lib/mysql:rw,delegated
    environment:
      MARIADB_PASSWORD: ninja
      MARIADB_ROOT_PASSWORD: ninja
      MARIADB_DATABASE: ninja
      MARIADB_USER: ninja
      PUID: "1026"
      PGID: "100"
    networks:
      - invoice_ninja_network

  cache:
    image: redis
    restart: unless-stopped
    healthcheck:
      test: ["CMD-SHELL", "redis-cliping||exit1"]
    networks:
      - invoice_ninja_network

  app:
    image: invoiceninja/invoiceninja:5
    restart: unless-stopped
    volumes:
      - ./public:/var/www/app/public:rw,delegated
      - ./storage:/var/www/app/storage:rw,delegated
    depends_on:
      db:
        condition: service_started
      cache:
        condition: service_healthy
    environment:
      APP_NAME: "InvoiceNinja"
      APP_ENV: "production"
      APP_KEY: base64:REPLACE_WITH_YOUR_GENERATED_KEY
      APP_DEBUG: "0"
      APP_URL: "https://invoiceninja.example.com"
      IS_DOCKER: "true"
      PHANTOMJS_PDF_GENERATION: "0"
      PDF_GENERATOR: "snappdf"
      TRUSTED_PROXIES: "*"
      MULTI_DB_ENABLED: "0"
      DB_HOST: db
      DB_DATABASE: ninja
      DB_USERNAME: ninja
      DB_PASSWORD: ninja
      DB_PORT: "3306"
      PUID: "1026"
      PGID: "100"
      CACHE_DRIVER: redis
      SESSION_DRIVER: redis
      REDIS_HOST: cache
      MAIL_MAILER: smtp
      MAIL_HOST: smtp.example.com
      MAIL_PORT: "465"
      MAIL_USERNAME: your-smtp-user
      MAIL_PASSWORD: your-smtp-password
      MAIL_ENCRYPTION: SSL
      MAIL_FROM_ADDRESS: "invoiceninja@example.com"
      MAIL_FROM_NAME: "YourName"
      REQUIRE_HTTPS: "1"
      NINJA_ENVIRONMENT: "selfhost"
      IN_USER_EMAIL: admin@example.com
      IN_PASSWORD: set-a-strong-password
    networks:
      - invoice_ninja_network

networks:
  invoice_ninja_network:
    driver: bridge
Enter fullscreen mode Exit fullscreen mode

Replace APP_KEY, APP_URL, the MARIADB_*/DB_* values, MAIL_* settings, and IN_USER_EMAIL/IN_PASSWORD before starting. Nginx proxies to the app container on port 9000.

Issue a certificate before bringing up Nginx (standalone mode needs port 80 free):

$sudo certbot certonly --standalone -d invoiceninja.example.com --email invoiceninja@example.com --agree-tos --non-interactive
Enter fullscreen mode Exit fullscreen mode

Create the vhost config:

$nano config/in-vhost.conf
Enter fullscreen mode Exit fullscreen mode
server {
    listen 80 default_server;
    server_name invoiceninja.example.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl default_server;
    server_name invoiceninja.example.com;

    server_tokens off;
    client_max_body_size 100M;

    ssl_certificate /etc/letsencrypt/live/invoiceninja.example.com/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/invoiceninja.example.com/privkey.pem;

    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_prefer_server_ciphers on;
    ssl_ciphers EECDH+AESGCM:EDH+AESGCM:AES256+EECDH:AES256+EDH;

    root /var/www/app/public/;
    index index.php;

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location = /favicon.ico { access_log off; log_not_found off; }
    location = /robots.txt  { access_log off; log_not_found off; }

    location ~* /storage/.*\.php$ {
        return 503;
    }

    location ~ \.php$ {
        fastcgi_split_path_info ^(.+\.php)(/.+)$;
        fastcgi_pass app:9000;
        fastcgi_index index.php;
        include fastcgi_params;
        fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
        fastcgi_intercept_errors off;
        fastcgi_buffer_size 16k;
        fastcgi_buffers 4 16k;
    }
}
Enter fullscreen mode Exit fullscreen mode
$sudo docker-compose up -d
$sudo docker container ls
Enter fullscreen mode Exit fullscreen mode

Automate cert renewal:

$sudo nano /etc/crontab
Enter fullscreen mode Exit fullscreen mode
0 0 * * * root certbot renew --quiet
Enter fullscreen mode Exit fullscreen mode

Use Invoice Ninja

  1. Visit https://invoiceninja.example.com/login, sign in with your admin credentials.
  2. On first login, set your company name and default currency.
  3. Fill in company details under Settings.
  4. Invoices → New InvoiceNew Client to add a customer.
  5. Set invoice date, due date, invoice number, PO, and discount fields.
  6. Add ItemNew Product to define line items with unit cost and quantity.
  7. Fill in notes/terms/footer, preview, then Save — download or print from there.

Next Steps

Invoice Ninja is running with either a native LEMP stack or a containerized Docker Compose setup. From here:

  • Configure recurring invoices and payment reminders under company settings
  • Connect a payment gateway (Stripe, PayPal) for online payment collection
  • Set up scheduled database backups given financial data lives here

For the full guide, visit the original article on Vultr Docs.

Source: dev.to

arrow_back Back to Tutorials