Consolidate nginx + php to single PHP/Apache container.

Additionally, fixed up a number of outstanding bugs such as permissions problems on files in the container, reverse proxies working at all, lack of automated setup, and prerequisite/configuration problems with some features (such as embedded JPEG logos on Invoices).
This commit is contained in:
James Harmison 2021-01-26 19:27:26 -05:00
parent 1550730208
commit f7db489875
No known key found for this signature in database
GPG Key ID: 32383B2D27A5D4B5
12 changed files with 307 additions and 123 deletions

4
.gitignore vendored Normal file
View File

@ -0,0 +1,4 @@
db.env
run.env
tags
*.swp

57
Dockerfile Normal file
View File

@ -0,0 +1,57 @@
FROM php:7.4-apache
ARG AKAUNTING_DOCKERFILE_VERSION=0.1
ARG SUPPORTED_LOCALES="en_US.UTF-8"
RUN apt-get update \
&& apt-get -y upgrade --no-install-recommends \
&& apt-get install -y \
build-essential \
imagemagick \
libfreetype6-dev \
libicu-dev \
libjpeg62-turbo-dev \
libjpeg-dev \
libmcrypt-dev \
libonig-dev \
libpng-dev \
libpq-dev \
libssl-dev \
libxml2-dev \
libxrender1 \
libzip-dev \
locales \
openssl \
unzip \
zip \
zlib1g-dev \
--no-install-recommends \
&& apt-get clean && rm -rf /var/lib/apt/lists/*
RUN for locale in ${SUPPORTED_LOCALES}; do \
sed -i 's/^# '"${locale}/${locale}/" /etc/locale.gen; done \
&& locale-gen
RUN docker-php-ext-configure gd \
--with-freetype \
--with-jpeg \
&& docker-php-ext-install -j$(nproc) \
gd \
bcmath \
intl \
mbstring \
pcntl \
pdo \
pdo_mysql \
zip
RUN mkdir -p /var/www/akaunting \
&& curl -Lo /tmp/akaunting.zip 'https://akaunting.com/download.php?version=latest&utm_source=docker&utm_campaign=developers' \
&& unzip /tmp/akaunting.zip -d /var/www/html \
&& rm -f /tmp/akaunting.zip
COPY files/akaunting.sh /usr/local/bin/akaunting.sh
COPY files/html /var/www/html
ENTRYPOINT ["/usr/local/bin/akaunting.sh"]
CMD ["--start"]

95
README.md Normal file
View File

@ -0,0 +1,95 @@
# Akaunting Docker Image
You can pull the latest image with `docker pull docker.io/akaunting/akaunting:latest`
## Description
This repository defines how the official Akaunting images are built for Docker Hub.
Akaunting is online, open source and free accounting software built with modern technologies. Track your income and expenses with ease. For more information on Akaunting, please visit the [website](https://akaunting.com).
## Prerequisites
1. docker-compose, or the knowhow to use docker or podman to run these images.
1. You'll need to use some other reverse proxy for TLS termination. HAProxy, Nginx, or Apache work fine and have integrations with [Let'sEncrypt](https://letsencrypt.org/) that let you request wildcard certificates. See [Reverse proxying for TLS termination](#reverse-proxying-for-tls-termination) for more information. Reverse proxies are all trusted in these images to support use cases like [Kubernetes Ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/).
1. Your own cache, if you need to scale to lots of users. Memcached and Redis are popular choices. See the [configuration for cache](https://github.com/akaunting/akaunting/blob/master/config/cache.php) and [redis](https://github.com/akaunting/akaunting/blob/master/config/database.php#L128) if necessary. You can provide the variables for configuration via [run.env](env/run.env.example).
## Usage
```shell
git clone https://github.com/akaunting/docker
cd docker
cp env/db.env.example env/db.env
vi env/db.env # and set things
cp env/run.env.example env/run.env
vi env/run.env # and set things
docker-compose up -d -e AKAUNTING_SETUP=true
```
Then head to HTTP at port 8080 on the docker-compose hostand finish configuring your Akaunting company through the interactive wizard.
After setup is complete, bring the containers down before bringing them back up without the setup variable.
```shell
docker-compose down -v
docker-compose up -d
```
Included is a [watchtower](https://containrrr.dev/watchtower/) container. This will automatically pull updates for the [MariaDB](https://hub.docker.com/_/mariadb) and [Akaunting](https://hub.docker.com/akaunting/akaunting) images daily, restarting the containers with the new images when there has been an update.
## Backup and restore
You could use something like the following commands to make backups for your deployment:
```shell
mkdir -p ~/backups
for volume in akaunting-data akaunting-db; do
docker run --rm -v $volume:/volume -v ~/backups:/backups alpine tar cvzf /backups/$volume-$(date +%Y-%m-%d).tgz -C /volume ./
done
```
In order to restore those backups, you would run something like:
```shell
backup=2021-01-26 # you should select the backup you want to restore here
for volume in akaunting-data akaunting-db; do
docker run --rm -v $volume:/volume -v ~/backups:/backups alpine sh -c "rm -rf /volume/* /volume/..?* /volume/.[!.]* ; tar xvzf /backups/$volume-$backup.tgz -C /volume"
done
```
## A note on upgrades
The upgrade between 2.0.26 and 2.1.0 broke some things in existing deployments due to a Laravel version migration in Akaunting. In order to fix this, you could have run something like the following:
```shell
docker exec -it akaunting bash
```
Then, inside the container, the following:
```shell
php artisan view:clear
```
Future version migrations might require something like:
```shell
php artisan migrate --force
```
Application upgrade/migration logic is not baked into this application image. An upgrade between versions that requires intervention would best be encapsulated in something like a [Kubernetes Operator](https://kubernetes.io/docs/concepts/extend-kubernetes/operator/), rather than adding to the complexity of the application image.
If you use these images in production, it is recommended to have a testing environment that draws automatic updates and exists so that you can validate the steps required to migrate between versions. Your production deployment should use pinned version tags, which are tagged based on the Akaunting release. Migrating your production version would then require manual intervention and enable you to take the manual steps necessary to complete the migration.
## Reverse proxying for TLS termination
No configuration has been provided for TLS termination. The "right" TLS termination strategy and load balancing setup is highly subjective and depends very much on the rest of your environment. The container image accepts connections from any proxy, and recognizes the host from HTTP requests via the APP_URL environment variable.
Googling "letsencrypt haproxy" returns with [a](https://serversforhackers.com/c/letsencrypt-with-haproxy) [number](https://gridscale.io/en/community/tutorials/haproxy-ssl/) [of](https://kevinbentlage.nl/blog/lets-encrypt-with-haproxy/) [articles](https://cheppers.com/how-https-haproxy-and-letsencrypt) with decent instructions on how to set up HAProxy and certbot to automatically renew certificates served. Some extra keywords, like [wildcard](https://nicklang.com/posts/letsencrypt-a-wildcard-cert-for-haproxy-to-use-in-docker-swarm), give enough extra information to layer that into your configuration. If nginx makes more sense for your environment, an nginx configuration that stitches together the elements of [load balancing](http://nginx.org/en/docs/http/load_balancing.html), [reverse proxying](https://timothy-quinn.com/using-nginx-as-a-reverse-proxy-for-multiple-sites/), and [Let'sEncrypt](https://www.digitalocean.com/community/tutorials/how-to-secure-nginx-with-let-s-encrypt-on-ubuntu-16-04) can meet the same need. So can Apache, or a number of other webservers. The world is your oyster.
A robust TLS termination and load-balancing setup should outperform, and be easier to maintain than, many little nginx containers running with their own certificate lifecycles, even if they're automated. Running Akaunting in a container makes the most sense if you're running lots of services on fewer hosts, and for that use case a load-balancing reverse proxy for TLS termination might make more sense. Or it might not. Now you get to choose.
## Languages
Right now, the only built language is US English. If you would like more supported locales to be built into the container image, please [open an issue](https://github.com/akaunting/docker/issues).

View File

@ -1,42 +1,35 @@
version: '3.7' version: '3.7'
services: services:
nginx:
build: ./nginx akaunting:
image: docker.io/akaunting/akaunting:latest
build:
context: .
ports: ports:
- 8080:80 - 8080:80
volumes: volumes:
- ./nginx/conf.d:/etc/nginx/conf.d:ro - akaunting-data:/var/www/html/storage
- app-data:/var/www/akaunting
restart: unless-stopped restart: unless-stopped
env_file:
php: - env/run.env
build: ./php
volumes:
- app-data:/var/www/akaunting
restart: unless-stopped
db:
image: mysql:latest
volumes:
- db-data:/var/lib/mysql
environment: environment:
MYSQL_ROOT_PASSWORD: akaunting_root_password - AKAUNTING_SETUP
MYSQL_DATABASE: akaunting_db
MYSQL_USER: akaunting_admin
MYSQL_PASSWORD: akaunting_password
command: --default-authentication-plugin=mysql_native_password
akaunting-db:
image: mariadb
volumes:
- akaunting-db:/var/lib/mysql
restart: unless-stopped
env_file:
- env/db.env
akaunting-update:
image: containrrr/watchtower
volumes:
- /var/run/docker.sock:/var/run/docker.sock
command: --cleanup akaunting akaunting-db
# image: postgres:alpine
# volumes:
# - db-data:/var/lib/postgresql/data
# environment:
# POSTGRES_DB: akaunting_db
# POSTGRES_USER: akaunting_admin
# POSTGRES_PASSWORD: akaunting_password
# ports:
# - "5432:5432"
volumes: volumes:
app-data: akaunting-data:
db-data: akaunting-db:

9
env/db.env.example vendored Normal file
View File

@ -0,0 +1,9 @@
# These could be changed
MYSQL_DATABASE=akaunting
MYSQL_USER=admin
# This should definitely be changed to something long and random
MYSQL_PASSWORD=akaunting_password
# You should probably leave this
MYSQL_RANDOM_ROOT_PASSWORD=yes

22
env/run.env.example vendored Normal file
View File

@ -0,0 +1,22 @@
# You should change this to match your reverse proxy DNS name and protocol
APP_URL=https://akaunting.example.com
LOCALE=en-US
# Don't change this unless you are using rootless podman without pod networking
DB_HOST=127.0.0.1
# Change these to match env/db.env
DB_DATABASE=akaunting
DB_USERNAME=admin
DB_PASSWORD=akaunting_password
# You should change this to a random string of three numbers or letters followed by an underscore
DB_PREFIX=asd_
# These define the first company to exist on this instance. They are only used during setup.
COMPANY_NAME=My Company
COMPANY_EMAIL=my@company.com
# This will be the first administrative user created on setup.
ADMIN_EMAIL=me@company.com
ADMIN_PASSWORD=password

63
files/akaunting.sh Executable file
View File

@ -0,0 +1,63 @@
#!/bin/bash -e
a2enmod rewrite
do_start=
do_shell=
do_setup=
while [ $# -gt 0 ]; do
case "$1" in
--start)
do_start=true
;;
--shell)
do_start=false
do_shell=true
;;
--setup)
do_setup=true
do_start=true
;;
esac
shift
done
mkdir -p storage/framework/{sessions,views,cache}
mkdir -p storage/app/uploads
if [ "$do_setup" -o "$AKAUNTING_SETUP" == "true" ]; then
retry_for=30
retry_interval=5
while sleep $retry_interval; do
if php artisan install \
--db-host=$DB_HOST \
--db-name=$DB_DATABASE \
--db-username=$DB_USERNAME \
"--db-password=$DB_PASSWORD" \
--db-prefix=$DB_PREFIX \
"--company-name=$COMPANY_NAME" \
"--company-email=$COMPANY_EMAIL" \
"--admin-email=$ADMIN_EMAIL" \
"--admin-password=$ADMIN_PASSWORD" \
"--locale=$LOCALE" --no-interaction; then break
else
if [ $retry_for -le 0 ]; then
echo "Unable to find database!" >&2
exit 1
fi
(( retry_for -= retry_interval ))
fi
done
else
unset COMPANY_NAME COMPANY_EMAIL ADMIN_EMAIL ADMIN_PASSWORD
fi
chmod -R u=rwX,g=rX,o=rX /var/www/html
chown -R www-data:root /var/www/html
if [ "$do_start" ]; then
exec docker-php-entrypoint apache2-foreground
elif [ "$do_shell" ]; then
exec /bin/bash -li
fi

27
files/html/.env Normal file
View File

@ -0,0 +1,27 @@
PP_NAME=Akaunting
APP_ENV=production
APP_LOCALE=en-US
APP_INSTALLED=true
APP_DEBUG=false
DB_CONNECTION=mysql
DB_PORT=3306
BROADCAST_DRIVER=log
CACHE_DRIVER=file
SESSION_DRIVER=file
QUEUE_CONNECTION=sync
LOG_CHANNEL=stderr
MAIL_MAILER=mail
MAIL_HOST=localhost
MAIL_PORT=2525
MAIL_USERNAME=null
MAIL_PASSWORD=null
MAIL_ENCRYPTION=null
MAIL_FROM_NAME=null
MAIL_FROM_ADDRESS=null
FIREWALL_ENABLED=true
MODEL_CACHE_ENABLED=true

View File

@ -0,0 +1,6 @@
<?php
return [
'proxies' => '*',
'headers' => Illuminate\Http\Request::HEADER_X_FORWARDED_ALL,
];

View File

@ -1,11 +0,0 @@
FROM nginx:alpine
RUN apk update && apk add --no-progress -q wget unzip
RUN wget -O akaunting.zip "https://akaunting.com/download.php?version=latest&utm_source=docker&utm_campaign=developers"
RUN mkdir -p /var/www/akaunting
RUN unzip akaunting.zip -d /var/www/akaunting
RUN rm akaunting.zip
RUN find /var/www/akaunting/ -type d -exec chmod 755 {} \;
RUN find /var/www/akaunting/ -type f -exec chmod 644 {} \;

View File

@ -1,48 +0,0 @@
server {
server_name akaunting;
listen 80 default_server;
access_log /dev/stdout;
error_log /dev/stdout;
root /var/www/akaunting;
index index.html index.htm index.php;
add_header X-Frame-Options "SAMEORIGIN";
add_header X-XSS-Protection "1; mode=block";
add_header X-Content-Type-Options "nosniff";
charset utf-8;
# Prevent Direct Access To Protected Files
location ~ \.(env|log) {
deny all;
}
# Prevent Direct Access To Protected Folders
location ~ ^/(^app$|bootstrap|config|database|overrides|resources|routes|storage|tests|artisan) {
deny all;
}
# Prevent Direct Access To modules/vendor Folders Except Assets
location ~ ^/(modules|vendor)\/(.*)\.((?!ico|gif|jpg|jpeg|png|js|css|less|sass|font|woff|woff2|eot|ttf|svg).)*$ {
deny all;
}
error_page 404 /index.php;
location / {
try_files $uri $uri/ /index.php?$query_string;
}
# Pass PHP Scripts To FastCGI Server
location ~ \.php$ {
try_files $uri =404;
fastcgi_split_path_info ^(.+\.php)(/.+)$;
fastcgi_pass php:9000;
fastcgi_index index.php;
include fastcgi_params;
fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name;
fastcgi_param PATH_INFO $fastcgi_path_info;
}
}

View File

@ -1,33 +0,0 @@
FROM php:7.4-fpm
RUN apt-get update && apt-get install -y \
libfreetype6-dev \
libjpeg62-turbo-dev \
libzip-dev \
imagemagick \
libmcrypt-dev \
libpng-dev \
libpq-dev \
libxrender1 \
locales \
openssh-client \
patch \
unzip \
zlib1g-dev \
zip \
--no-install-recommends && \
apt-get clean && rm -rf /var/lib/apt/lists/*
RUN docker-php-ext-configure gd --with-freetype --with-jpeg
RUN cp /usr/share/i18n/SUPPORTED /etc/locale.gen
RUN locale-gen
RUN docker-php-ext-install \
gd \
bcmath \
pcntl \
pdo \
pdo_pgsql \
pdo_mysql \
zip