diff --git a/.dockerignore b/.dockerignore new file mode 100644 index 00000000..3d889cfd --- /dev/null +++ b/.dockerignore @@ -0,0 +1,16 @@ +.git +.github +tests +.dockerignore +.editorconfig +.env.example +.env.testing +.gitattributes +.gitignore +.styleci.yml +.travis.yml +changelog.md +Dockerfile +LICENSE +README.md +webpack.mix.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 00000000..a7594138 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,114 @@ +name: CI +on: + push: + paths: + - .github/workflows/ci.yml + - app/** + - bootstrap/** + - config/** + - database/** + - docker/** + - public/** + - resources/** + - routes/** + - storage/** + - tests/** + - .dockerignore + - .env.travis + - artisan + - composer.json + - composer.lock + - Dockerfile + - phpunit.xml + - server.php + pull_request: + paths: + - .github/workflows/ci.yml + - app/** + - bootstrap/** + - config/** + - database/** + - docker/** + - public/** + - resources/** + - routes/** + - storage/** + - tests/** + - .dockerignore + - .env.travis + - artisan + - composer.json + - composer.lock + - Dockerfile + - phpunit.xml + - server.php + +jobs: + verify: + runs-on: ubuntu-latest + env: + DOCKER_BUILDKIT: "1" + steps: + - uses: actions/checkout@v2.3.4 + + - name: Build test image + run: docker build --target test -t test-container . + + - name: Run tests in test container + run: | + touch coverage.txt + docker run --rm \ + test-container + + - name: Build final image + run: docker build . + + publish: + needs: [verify] + if: github.event_name == 'push' && github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2.3.4 + with: + ref: ${{github.event.pull_request.head.ref}} + repository: ${{github.event.pull_request.head.repo.full_name}} + + - uses: docker/setup-qemu-action@v1 + - uses: docker/setup-buildx-action@v1 + + - uses: docker/login-action@v1 + with: + username: 2fauth + password: ${{ secrets.DOCKERHUB_PASSWORD }} + + - name: Set variables + id: vars + env: + EVENT_NAME: ${{ github.event_name }} + run: | + BRANCH=${GITHUB_REF#refs/heads/} + TAG=${GITHUB_REF#refs/tags/} + echo ::set-output name=commit::$(git rev-parse --short HEAD) + echo ::set-output name=created::$(date -u +%Y-%m-%dT%H:%M:%SZ) + if [ "$TAG" != "$GITHUB_REF" ]; then + echo ::set-output name=version::$TAG + echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7 + elif [ "$BRANCH" = "master" ]; then + echo ::set-output name=version::latest + echo ::set-output name=platforms::linux/amd64,linux/386,linux/arm64,linux/arm/v6,linux/arm/v7 + else + echo ::set-output name=version::$BRANCH + echo ::set-output name=platforms::linux/amd64 + fi + + - name: Build and push final image + uses: docker/build-push-action@v2.6.1 + with: + platforms: ${{ steps.vars.outputs.platforms }} + build-args: | + CREATED=${{ steps.vars.outputs.created }} + COMMIT=${{ steps.vars.outputs.commit }} + VERSION=${{ steps.vars.outputs.version }} + tags: | + 2fauth/2fauth:${{ steps.vars.outputs.version }} + push: true diff --git a/.github/workflows/dockerhub-readme.yml b/.github/workflows/dockerhub-readme.yml new file mode 100644 index 00000000..9079a189 --- /dev/null +++ b/.github/workflows/dockerhub-readme.yml @@ -0,0 +1,22 @@ +name: Docker Hub description +on: + push: + branches: [master] + paths: + - docker/README.md + - .github/workflows/dockerhub-readme.yml +jobs: + dockerHubDescription: + if: github.event.pull_request.head.repo.full_name == github.repository + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v2.3.4 + - name: Docker Hub Description + uses: peter-evans/dockerhub-description@v2.4.3 + with: + username: 2fauth + password: ${{ secrets.DOCKERHUB_PASSWORD }} + repository: 2fauth/2fauth + short-description: A web app to manage your Two-Factor Authentication (2FA) accounts and generate their security codes + readme-filepath: docker/README.md diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..96b8e6fe --- /dev/null +++ b/Dockerfile @@ -0,0 +1,185 @@ +ARG BUILDPLATFORM=linux/amd64 +ARG TARGETPLATFORM +ARG ALPINE_VERSION=3.14 +ARG PHP_VERSION=7.3-alpine${ALPINE_VERSION} +ARG COMPOSER_VERSION=2.1 +ARG SUPERVISORD_VERSION=v0.7.3 + +FROM --platform=${BUILDPLATFORM} composer:${COMPOSER_VERSION} AS build-composer +FROM composer:${COMPOSER_VERSION} AS composer +FROM qmcgaw/binpot:supervisord-${SUPERVISORD_VERSION} AS supervisord + +FROM --platform=${BUILDPLATFORM} php:${PHP_VERSION} AS vendor +COPY --from=build-composer --chown=${UID}:${GID} /usr/bin/composer /usr/bin/composer +RUN apk add --no-cache unzip +WORKDIR /srv +COPY artisan composer.json composer.lock ./ +COPY database ./database +RUN composer install --prefer-dist --no-scripts --no-dev --no-autoloader +RUN composer dump-autoload --no-scripts --no-dev --optimize + +FROM --platform=${BUILDPLATFORM} vendor AS test +COPY . . +RUN mv .env.travis .env +RUN composer install +RUN php artisan key:generate +ENTRYPOINT [ "/srv/vendor/bin/phpunit" ] + +FROM alpine:${ALPINE_VERSION} + +ARG UID=1000 +ARG GID=1000 + +# Composer 2 +COPY --from=composer --chown=${UID}:${GID} /usr/bin/composer /usr/bin/composer +# Supervisord from https://github.com/ochinchina/supervisord +COPY --from=supervisord --chown=${UID}:${GID} /bin /usr/local/bin/supervisord + +# Install PHP and PHP system dependencies +RUN apk add --update --no-cache \ + # PHP + php7 \ + # Composer dependencies + php7-phar \ + # PHP SQLite driver + php7-pdo_sqlite php7-sqlite3 \ + # PHP extensions + php7-xml php7-gd php7-mbstring \ + # Runtime dependencies + php7-session php7-json php7-openssl \ + # Nginx and PHP FPM to serve over HTTP + php7-fpm nginx \ + && \ + # Clean up + rm /etc/nginx/nginx.conf && \ + # Fix ownership to ${UID}:${GID} + chown -R ${UID}:${GID} /var/lib/nginx/ + +# PHP FPM configuration +# Change username and ownership in php-fpm pool config +RUN sed -i '/user = nobody/d' /etc/php7/php-fpm.d/www.conf && \ + sed -i '/group = nobody/d' /etc/php7/php-fpm.d/www.conf && \ + sed -i '/listen.owner/d' /etc/php7/php-fpm.d/www.conf && \ + sed -i '/listen.group/d' /etc/php7/php-fpm.d/www.conf +# Pre-create files with the correct permissions +RUN mkdir /run/php && \ + chown ${UID}:${GID} /run/php /var/log/php7 && \ + chmod 700 /run/php /var/log/php7 + +# Nginx configuration +EXPOSE 8000/tcp +RUN touch /run/nginx/nginx.pid /var/lib/nginx/logs/error.log && \ + chown ${UID}:${GID} /run/nginx/nginx.pid /var/lib/nginx/logs/error.log +COPY --chown=${UID}:${GID} docker/nginx.conf /etc/nginx/nginx.conf +RUN nginx -t + +# Supervisord configuration +COPY --chown=${UID}:${GID} docker/supervisord.conf /etc/supervisor/supervisord.conf + +# Create end user directory +RUN mkdir -p /2fauth && \ + chown -R ${UID}:${GID} /2fauth && \ + chmod 700 /2fauth + +# Create /srv internal directory +WORKDIR /srv +RUN chown -R ${UID}:${GID} /srv && \ + chmod 700 /srv + +# Run without root +USER ${UID}:${GID} + +# Dependencies +COPY --from=vendor --chown=${UID}:${GID} /srv/vendor /srv/vendor + +# Copy the rest of the code +COPY --chown=${UID}:${GID} . . +# RUN composer dump-autoload --no-scripts --no-dev --optimize + +# Entrypoint +ENTRYPOINT [ "/usr/local/bin/entrypoint.sh" ] +COPY --chown=${UID}:${GID} docker/entrypoint.sh /usr/local/bin/entrypoint.sh +RUN chmod 500 /usr/local/bin/entrypoint.sh + +ENV \ + # You can change the name of the app + APP_NAME=2FAuth \ + # You can leave this on "local". If you change it to production most console commands will ask for extra confirmation. + # Never set it to "testing". + APP_ENV=local \ + # Set to true if you want to see debug information in error screens. + APP_DEBUG=false \ + # This should be your email address + SITE_OWNER=mail@example.com \ + # The encryption key for our database and sessions. Keep this very secure. + # If you generate a new one all existing data must be considered LOST. + # Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it + APP_KEY=SomeRandomStringOf32CharsExactly \ + # This variable must match your installation's external address but keep in mind that + # it's only used on the command line as a fallback value. + APP_URL=http://localhost \ + # Turn this to true if you want your app to react like a demo. + # The Demo mode reset the app content every hours and set a generic demo user. + IS_DEMO_APP=false \ + # The log channel defines where your log entries go to. + # 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. + # Several other options exist. You can use 'single' for one big fat error log (not recommended). + # Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself. + LOG_CHANNEL=daily \ + # Log level. You can set this from least severe to most severe: + # debug, info, notice, warning, error, critical, alert, emergency + # If you set it to debug your logs will grow large, and fast. If you set it to emergency probably + # nothing will get logged, ever. + APP_LOG_LEVEL=notice \ + # Database config & credentials + # DB_CONNECTION can only be sqlite + DB_CONNECTION=sqlite \ + DB_DATABASE="/srv/database/database.sqlite" \ + # If you're looking for performance improvements, you could install memcached. + CACHE_DRIVER=file \ + SESSION_DRIVER=file \ + # Mail settings + # Refer your email provider documentation to configure your mail settings + # Set a value for every available setting to avoid issue + MAIL_DRIVER=log \ + MAIL_HOST=smtp.mailtrap.io \ + MAIL_PORT=2525 \ + MAIL_FROM=changeme@example.com \ + MAIL_USERNAME=null \ + MAIL_PASSWORD=null \ + MAIL_ENCRYPTION=null \ + MAIL_FROM_NAME=null \ + MAIL_FROM_ADDRESS=null \ + # Leave the following configuration vars as is. + # Unless you like to tinker and know what you're doing. + BROADCAST_DRIVER=log \ + QUEUE_DRIVER=sync \ + SESSION_LIFETIME=12 \ + REDIS_HOST=127.0.0.1 \ + REDIS_PASSWORD=null \ + REDIS_PORT=6379 \ + PUSHER_APP_ID= \ + PUSHER_APP_KEY= \ + PUSHER_APP_SECRET= \ + PUSHER_APP_CLUSTER=mt1 \ + MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" \ + MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" \ + MIX_ENV=local + +ARG VERSION=unknown +ARG CREATED="an unknown date" +ARG COMMIT=unknown +ENV \ + VERSION=${VERSION} \ + CREATED=${CREATED} \ + COMMIT=${COMMIT} +LABEL \ + org.opencontainers.image.authors="https://github.com/Bubka" \ + org.opencontainers.image.version=$VERSION \ + org.opencontainers.image.created=$CREATED \ + org.opencontainers.image.revision=$COMMIT \ + org.opencontainers.image.url="https://github.com/Bubka/2FAuth" \ + org.opencontainers.image.documentation="https://hub.docker.com/r/2fauth/2fauth" \ + org.opencontainers.image.source="https://github.com/Bubka/2FAuth" \ + org.opencontainers.image.title="2fauth" \ + org.opencontainers.image.description="A web app to manage your Two-Factor Authentication (2FA) accounts and generate their security codes" diff --git a/README.md b/README.md index 52fca5ab..b9d7e73e 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # 2FAuth ![https://travis-ci.com/github/Bubka/2FAuth](https://img.shields.io/travis/com/bubka/2fauth?style=flat-square) +[![Docker build status](https://github.com/Bubka/2fauth/actions/workflows/ci.yml/badge.svg)](https://github.com/Bubka/2fauth/actions/workflows/ci.yml) ![https://codecov.io/gh/Bubka/2FAuth](https://img.shields.io/codecov/c/github/Bubka/2FAuth?style=flat-square) ![https://github.com/Bubka/2FAuth/blob/master/LICENSE](https://img.shields.io/github/license/Bubka/2FAuth.svg?style=flat-square) @@ -10,6 +11,8 @@ A web app to manage your Two-Factor Authentication (2FA) accounts and generate t [**2FAuth Demo**](https://demo.2fauth.app/) +[**Use it with Docker**](docker) + Credentials (login - password) : *demo@2fauth.app* - *demo* ## Purpose diff --git a/docker/README.md b/docker/README.md new file mode 100644 index 00000000..df16ac28 --- /dev/null +++ b/docker/README.md @@ -0,0 +1,117 @@ +# Docker + +[![Build status](https://github.com/Bubka/2fauth/actions/workflows/ci.yml/badge.svg)](https://github.com/Bubka/2fauth/actions/workflows/ci.yml) + +[![dockeri.co](https://dockeri.co/image/2fauth/2fauth)](https://hub.docker.com/r/2fauth/2fauth) + +You can run 2fauth in a single Docker container. + +## Features + +- [![Latest size](https://img.shields.io/docker/image-size/2fauth/2fauth/latest?label=Image%20size)](https://hub.docker.com/r/2fauth/2fauth/tags) +- Compatible with: `amd64`, `386`, `arm64`, `arm/v6` and `arm/v7` +- Stores data in an Sqlite database file +- Runs without root as user with id `1000` and group id `1000` + +## Setup + +We assume your current directory is `/yourpath`. + +1. Create a directory on your host: + + ```sh + mkdir 2fauth + ``` + +1. **If your host is not Windows**: since the container runs without root as user `1000:1000`, you need to fix the ownership and permissions of that directory: + + ```sh + chown 1000:1000 2fauth + chmod 700 2fauth + ``` + + 💁 if you feel like using another ID, you can [build the image with build arguments](#Build-the-image-with-build-arguments). + +1. Run the container interactively: + + ```sh + docker run -it --rm -p 8000:8000/tcp \ + -v /yourpath/2fauth:/2fauth 2fauth/2fauth + ``` + +1. Access it at [http://localhost:8000](http://localhost:8000) + +You can stop it with `CTRL+C`. + +- You can also run it in the background by replacing `-it --rm` with `-d`. +- You can set environment variables available (see the [.env.example](.env.example)) with `-e`, for example `-e APP_NAME=2FAuth`. +- You can also use the [docker-compose.yml](docker-compose.yml) with `docker-compose` and modify it as you wish. + +### Use an existing SQLite file + +If you already have an SQLite file, move it to `/yourpath/2fauth/database.sqlite` on your host before starting the container. Don't forget to fix its ownership and permissions if you run on *nix: + +```sh +chown 1000:1000 /yourpath/2fauth/database.sqlite +chmod 700 /yourpath/2fauth/database.sqlite +``` + +The container will automagically pick it up. + +## Update + +⚠️ At the very least, backup your `database.sqlite` file to avoid bad surprises! + +The Docker image `2fauth/2fauth` is built on every commit pushed to the `master` branch. + +You can therefore pull the image with `docker pull 2fauth/2fauth` and restart the container to update it. + +You can also use tagged images, see [Docker Hub tags](https://hub.docker.com/r/2fauth/2fauth/tags?page=1&ordering=last_updated) which are produced on Github releases. + +## Build the image + +You can build the image from the `master` branch with `docker` and `git` using: + +```sh +docker build -t 2fauth/2fauth https://github.com/Bubka/2FAuth.git +``` + +### Build the image for a specific release + +You can build a [specific release](https://github.com/Bubka/2FAuth/releases) by appending the release tag with `#` to the command. For example: + +```sh +docker build -t 2fauth/2fauth https://github.com/Bubka/2FAuth.git#v2.1.0 +``` + +### Build the image for a specific commit + +You can build a specific commit (see [master's commits](https://github.com/Bubka/2FAuth/commits/master)) by appending the commit hash with `#` to the command. For example: + +```sh +docker build -t 2fauth/2fauth https://github.com/Bubka/2FAuth.git#fba9e29bd4e3bb697296bb0bde60ae869537528b +``` + +### Build the image with build arguments + +There are the following build arguments you can use to customize the image using `--build-arg key=value`: + +| Build argument | Default | Description | +| --- | --- | --- | +| `UID` | 1000 | The UID of the user to run the container as | +| `GID` | 1000 | The GID of the user to run the container as | +| `DEBIAN_VERSION` | `buster-slim` | The Debian version to use | +| `PHP_VERSION` | `7.3-buster` | The PHP version to use to get composer dependencies | +| `COMPOSER_VERSION` | `2.1` | The version of composer to use | +| `SUPERVISORD_VERSION` | `v0.7.3` | The version of supervisord to use | +| `VERSION` | `unknown` | The version of the image | +| `CREATED` | `an unknown date` | The date of the image build time | +| `COMMIT` | `unknown` | The commit hash of the Git commit used | + +## Implementation details + +- The final Docker image is based on `alpine:3.14` with minimal packages installed +- The container runs [`supervisord`](https://github.com/ochinchina/supervisord) to handle both an Nginx server and a PHP-FPM server together +- The `/srv` directory holds the repository data and PHP code. +- The `/2fauth` directory is targeted for the container end users. +- By default the container logs the Nginx logs and the PHP-FPM logs. The application logs (if any) can be found in `/2fauth/storage/logs`. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 00000000..299ac29e --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,71 @@ +version: "3" +services: + 2fauth: + image: 2fauth/2fauth + container_name: 2fauth + volumes: + - ./2fauth:/2fauth + ports: + - 8000:8000/tcp + environment: + # You can change the name of the app + - APP_NAME=2FAuth + # You can leave this on "local". If you change it to production most console commands will ask for extra confirmation. + # Never set it to "testing". + - APP_ENV=local + # Set to true if you want to see debug information in error screens. + - APP_DEBUG=false + # This should be your email address + - SITE_OWNER=mail@example.com + # The encryption key for our database and sessions. Keep this very secure. + # If you generate a new one all existing data must be considered LOST. + # Change it to a string of exactly 32 chars or use command `php artisan key:generate` to generate it + - APP_KEY=SomeRandomStringOf32CharsExactly + # This variable must match your installation's external address but keep in mind that + # it's only used on the command line as a fallback value. + - APP_URL=http://localhost + # Turn this to true if you want your app to react like a demo. + # The Demo mode reset the app content every hours and set a generic demo user. + - IS_DEMO_APP=false + # The log channel defines where your log entries go to. + # 'daily' is the default logging mode giving you 5 daily rotated log files in /storage/logs/. + # Several other options exist. You can use 'single' for one big fat error log (not recommended). + # Also available are 'syslog', 'errorlog' and 'stdout' which will log to the system itself. + - LOG_CHANNEL=daily + # Log level. You can set this from least severe to most severe: + # debug, info, notice, warning, error, critical, alert, emergency + # If you set it to debug your logs will grow large, and fast. If you set it to emergency probably + # nothing will get logged, ever. + - APP_LOG_LEVEL=notice + # Database config (can only be sqlite) + - DB_DATABASE="/srv/database/database.sqlite" + # If you're looking for performance improvements, you could install memcached. + - CACHE_DRIVER=file + - SESSION_DRIVER=file + # Mail settings + # Refer your email provider documentation to configure your mail settings + # Set a value for every available setting to avoid issue + - MAIL_DRIVER=log + - MAIL_HOST=smtp.mailtrap.io + - MAIL_PORT=2525 + - MAIL_FROM=changeme@example.com + - MAIL_USERNAME=null + - MAIL_PASSWORD=null + - MAIL_ENCRYPTION=null + - MAIL_FROM_NAME=null + - MAIL_FROM_ADDRESS=null + # Leave the following configuration vars as is. + # Unless you like to tinker and know what you're doing. + - BROADCAST_DRIVER=log + - QUEUE_DRIVER=sync + - SESSION_LIFETIME=12 + - REDIS_HOST=127.0.0.1 + - REDIS_PASSWORD=null + - REDIS_PORT=6379 + - PUSHER_APP_ID= + - PUSHER_APP_KEY= + - PUSHER_APP_SECRET= + - PUSHER_APP_CLUSTER=mt1 + - MIX_PUSHER_APP_KEY="${PUSHER_APP_KEY}" + - MIX_PUSHER_APP_CLUSTER="${PUSHER_APP_CLUSTER}" + - MIX_ENV=local diff --git a/docker/entrypoint.sh b/docker/entrypoint.sh new file mode 100644 index 00000000..e5ea071a --- /dev/null +++ b/docker/entrypoint.sh @@ -0,0 +1,45 @@ +#!/bin/sh + +set -e + +echo "Running version ${VERSION} commit ${COMMIT} built on ${CREATED}" + +# Show versions +echo "supervisord version: $(supervisord version)" +php-fpm7 -v | head -n 1 +nginx -v + +if [ "${DB_CONNECTION}" = "sqlite" ]; then + if [ ! -f /2fauth/database.sqlite ]; then + touch /2fauth/database.sqlite + fi + rm -f /srv/database/database.sqlite + ln -s /2fauth/database.sqlite /srv/database/database.sqlite +fi + +# Inject storage in /2fauth and use it with a symlink +if [ ! -d /2fauth/storage ]; then + mv /srv/storage /2fauth/storage +else + rm -r /srv/storage +fi +ln -s /2fauth/storage /srv/storage + +# Note: ${COMMIT} is set by the CI +if [ -f /2fauth/installed ]; then + INSTALLED_COMMIT="$(cat /2fauth/installed)" + if [ "${INSTALLED_COMMIT}" != "${COMMIT}" ]; then + echo "Installed commit ${INSTALLED_COMMIT} is different from program commit ${COMMIT}, we are migrating..." + php artisan migrate + php artisan config:clear + fi +else + php artisan migrate:refresh + php artisan passport:install +fi + +echo "${COMMIT}" > /2fauth/installed +php artisan storage:link +php artisan config:cache + +supervisord diff --git a/docker/nginx.conf b/docker/nginx.conf new file mode 100644 index 00000000..defee915 --- /dev/null +++ b/docker/nginx.conf @@ -0,0 +1,36 @@ +events {} +http { + include mime.types; + + access_log /dev/stdout; + error_log /dev/stderr; + + server { + listen 8000; + server_name 2fAuth; + root /srv/public; + + index index.php; + + charset utf-8; + + 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; } + + error_page 404 /index.php; + + location ~ \.php$ { + fastcgi_pass 127.0.0.1:9000; + fastcgi_param SCRIPT_FILENAME $realpath_root$fastcgi_script_name; + include fastcgi_params; + } + + location ~ /\.(?!well-known).* { + deny all; + } + } +} diff --git a/docker/supervisord.conf b/docker/supervisord.conf new file mode 100644 index 00000000..c685ff0d --- /dev/null +++ b/docker/supervisord.conf @@ -0,0 +1,19 @@ +[supervisord] +nodaemon=true +pidfile=/run/supervisord.pid +loglevel=info + +[program-default] +stdout_logfile=/dev/stdout +stdout_logfile_maxbytes=0 +stderr_logfile=/dev/stderr +stderr_logfile_maxbytes=0 +autorestart=false +startretries=0 + +[program:php-fpm] +command=/usr/sbin/php-fpm7 -F + +[program:nginx] +command=/usr/sbin/nginx -g 'daemon off;' +depends_on=php-fpm