Docker Deploy Invoice Ninja
The easiest way to deploy Invoice Ninja is through docker which I will show you below. However if you wish to manually deploy it, this guide might be useful.
Requirements
I managed to deploy on a VM with only 512 MB of RAM. However, because we will be using a database container, and MySQL has a habbit of growing in memory, I would recommend deploying on a 1GB instance.
Steps
Grab the example.
#!/bin/bash
mkdir -p $HOME/invoice-ninja/src
git clone https://github.com/invoiceninja/dockerfiles.git $HOME/invoice-ninja/src/.
mv $HOME/invoice-ninja/src/docker-compose/.env.example $HOME/invoice-ninja/env
mv $HOME/invoice-ninja/src/docker-compose/nginx.conf $HOME/invoice-ninja/nginx.conf
mv $HOME/invoice-ninja/src/docker-compose/docker-compose.yml $HOME/invoice-ninja/.
rm -rf $HOME/invoice-ninja/src/
You should now have a folder called invoice-ninja
in your home directory which contains the following files:
- docker-compose.yml
- env
- nginx.conf
Edit Environment Variables
The env file will look like below (unless it has changed since I wrote this tutorial).
MYSQL_DATABASE=ninja
MYSQL_ROOT_PASSWORD=pwd
APP_DEBUG=0
APP_URL=http://localhost:8000
APP_KEY=SomeRandomStringSomeRandomString
APP_CIPHER=AES-256-CBC
DB_USERNAME=root
DB_PASSWORD=pwd
DB_HOST=db
DB_DATABASE=ninja
MAIL_HOST=mail.service.host
MAIL_USERNAME=username
MAIL_PASSWORD=password
MAIL_DRIVER=smtp
MAIL_FROM_NAME="My name"
MAIL_FROM_ADDRESS=user@mail.com
Run the following script to set random passwords in the env file:
#!/bin/bash
DB_PASSWORD=`openssl rand -base64 12`
APP_KEY=`openssl rand -base64 24`
FILEPATH="$HOME/invoice-ninja/env"
# Update the app key
SEARCH="APP_KEY=SomeRandomStringSomeRandomString"
REPLACE="APP_KEY=$APP_KEY"
sed -i "s;$SEARCH;$REPLACE;" $FILEPATH
# Update mysql root password
SEARCH="MYSQL_ROOT_PASSWORD=pwd"
REPLACE="MYSQL_ROOT_PASSWORD=$DB_PASSWORD"
sed -i "s;$SEARCH;$REPLACE;" $FILEPATH
# Update app db password setting to be the same
SEARCH="DB_PASSWORD=pwd"
REPLACE="DB_PASSWORD=$DB_PASSWORD"
sed -i "s;$SEARCH;$REPLACE;" $FILEPATH
It's very important to change the the following:
- Change the
APP_URL
to your hostname or IP and remove the:8000
- Update the
MAIL_
items to your SMTP details.
It is important to ensure the following is the case:
APP_DEBUG
is0
- The
APP_KEY
has been updated to a random string. E.g should not be:SomeRandomStringSomeRandomString
- The
DB_PASSWORD
andMYSQL_ROOT_PASSWORD
have been updated to a random string and not set topwd
.
By the end of it, you should have something like:
MYSQL_DATABASE=ninja
MYSQL_ROOT_PASSWORD=Htw0GOdwCF6NNt3E
APP_DEBUG=0
APP_URL=http://invoices.mydomain.com
APP_KEY=rfQH/9dZTjO4v4gARlnTz4oBrgin28qD
APP_CIPHER=AES-256-CBC
DB_USERNAME=root
DB_PASSWORD=Htw0GOdwCF6NNt3E
DB_HOST=db
DB_DATABASE=ninja
MAIL_HOST=smtp.gmail.com
MAIL_USERNAME=my.email@gmail.com
MAIL_PASSWORD=myAppSpecificPassword
MAIL_DRIVER=smtp
MAIL_FROM_NAME="Programster Blogs"
MAIL_FROM_ADDRESS=my.email@gmail.com
Edit docker-compose.yml
Now go into your docker-compose.yml
file and change:
- the database password to match whatever you set in the env file in the previous step.
.env
toenv
.- the port line from
8000:80
to80:80
.- This will allow the site to be available without needing to enter a port into the URL bar.
You should end up with a docker-compose file that looks like the following:
version: '3.6'
volumes:
db:
storage:
logo:
public:
# uncomment if you want to use external network (example network: "web")
#networks:
# web:
# external: true
services:
db:
image: mysql:5
env_file: env
restart: always
volumes:
- db:/var/lib/mysql
networks:
- default
app:
image: invoiceninja/invoiceninja
env_file: env
restart: always
depends_on:
- db
volumes:
- storage:/var/www/app/storage
- logo:/var/www/app/public/logo
- public:/var/www/app/public
networks:
- default
web:
image: nginx:1
volumes:
- ./nginx.conf:/etc/nginx/nginx.conf:ro
- storage:/var/www/app/storage
- logo:/var/www/app/public/logo
- public:/var/www/app/public
expose: # Expose ports without publishing them to the host machine - they’ll only be accessible to linked services.
- "80"
depends_on:
- app
ports: # Delete if you want to use reverse proxy
- 80:80
networks:
# - web # uncomment if you want to use external network (reverse proxy for example)
- default
cron:
image: invoiceninja/invoiceninja
env_file: env
volumes:
- storage:/var/www/app/storage
- logo:/var/www/app/public/logo
- public:/var/www/app/public
entrypoint: |
bash -c 'bash -s <<EOF
trap "break;exit" SIGHUP SIGINT SIGTERM
sleep 300s
while /bin/true; do
./artisan ninja:send-invoices
./artisan ninja:send-reminders
sleep 1d
done
EOF'
networks:
- default
Deploy!
Now you can deploy with the following command:
docker-compose up
Web UI Setup
Now navigate to your server's IP or hostname and you should be presented with a form to fill in like below:

Most of the details should already be filled in for you (from the environment file earlier). You just need to enter your name details and submit.
It would be a good idea to make use of button to send a test email to check your SMTP details are correct.
State
All state is held in the docker volumes called: * db * storage * logo * public
You will have seen these at the top of the docker-compose file section like so:
volumes:
db:
storage:
logo:
public:
You can use the docker volume
commands to interact with them. E.g. list them with:
docker volume ls
The actual data will default to being located under:
/var/lib/docker/volumes
Debugging
If you do not see the Web UI Setup form then something went wrong. It would be a good idea to re-deploy with APP_DEBUG set to 1 and then you will get an error message in the web interface. It is also good to read the text that appears in the CLI after you ran docker-compose up
.
Common issues are:
- Database password not matching in the compose file and the environment file.
- The APP_KEY string not being long enough.
Conclusion
You should now have your own open source invoice solution. In future I will show how to get SSL enabled.
Until then, please bear in mind that you will need to add the TRUSTED_PROXIES="192.168.x.y"
to your environment file if you are running SSL termination on your proxy.
References
First published: 16th August 2018