Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
17 changes: 9 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,15 @@ with [FrankenPHP](https://frankenphp.dev) and [Caddy](https://caddyserver.com/)
1. [Options available](docs/options.md)
2. [Using Symfony Docker with an existing project](docs/existing-project.md)
3. [Support for extra services](docs/extra-services.md)
4. [Deploying in production](docs/production.md)
5. [Debugging with Xdebug](docs/xdebug.md)
6. [TLS Certificates](docs/tls.md)
7. [Using MySQL instead of PostgreSQL](docs/mysql.md)
8. [Using Alpine Linux instead of Debian](docs/alpine.md)
9. [Using a Makefile](docs/makefile.md)
10. [Updating the template](docs/updating.md)
11. [Troubleshooting](docs/troubleshooting.md)
4. [Run as non-root](docs/non-root-user.md)
5. [Deploying in production](docs/production.md)
6. [Debugging with Xdebug](docs/xdebug.md)
7. [TLS Certificates](docs/tls.md)
8. [Using MySQL instead of PostgreSQL](docs/mysql.md)
9. [Using Alpine Linux instead of Debian](docs/alpine.md)
10. [Using a Makefile](docs/makefile.md)
11. [Updating the template](docs/updating.md)
12. [Troubleshooting](docs/troubleshooting.md)

## License

Expand Down
153 changes: 153 additions & 0 deletions docs/non-root-user.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
# Non-root user

Following [Docker best practices](https://docs.docker.com/build/building/best-practices/#user), it is recommended to run your services as non-root user whenever possible.

## Apply changes

You can apply the following patches to your `Dockerfile`, `compose.override.yaml` and `compose.prod.yaml` to run the FrankenPHP container as non-root for development and production usage.

`Dockerfile`

```diff
+ARG PUID=${PUID:-1000}
+ARG PGID=${PGID:-1000}
+ARG USER=${USER:-frankenphp}
+ARG GROUP=${GROUP:-frankenphp}

# Versions
FROM dunglas/frankenphp:1-php8.4 AS frankenphp_upstream

# Base FrankenPHP image
FROM frankenphp_upstream AS frankenphp_base

+ARG PUID
+ARG PGID
+ARG USER
+ARG GROUP
+
WORKDIR /app

-VOLUME /app/var/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why shouldn't the /app/var directory be a volume?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

While there are multiple considerations on why not to use VOLUME in the Dockerfile 1, regarding the scope of the this feature, if docker would not be run as "rootless" (not to be confused with "running docker as non-root user"), if a volume mounted ./:/app (on development) but the ./var directory does not exist on host, this will create the ./var directory as root on host (which prevents the application from starting as the container user can not write to ./var).

Think this would either require the ./var to already be present on host (untested), or remove the VOLUME directive as done now (or the user should use docker "rootless" but this comes with possible side-effects). Maybe using the VOLUME would only be really useful for prod stage (for docker/compose in production; not kubernetes).

Footnotes

  1. https://stackoverflow.com/a/55516433/9128538

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Interesting note. I can't find any simple ways to make this work while keeping this volume.


# ...

+RUN set -eux; \
+ groupadd -g $PGID $GROUP; \
+ useradd -u $PUID -g $PGID --no-create-home $USER; \
+ mkdir -p var/cache var/log; \
+ chown -R $PUID:$PGID /data/ /config/ var/
+
ENTRYPOINT ["docker-entrypoint"]

# ...

# Dev FrankenPHP image
FROM frankenphp_base AS frankenphp_dev

+ARG USER
+
ENV APP_ENV=dev
ENV XDEBUG_MODE=off
ENV FRANKENPHP_WORKER_CONFIG=watch

COPY --link frankenphp/conf.d/20-app.dev.ini $PHP_INI_DIR/app.conf.d/

+USER $USER
+
CMD [ "frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--watch" ]

# Prod FrankenPHP image
FROM frankenphp_base AS frankenphp_prod

+ARG PUID
+ARG PGID
+ARG USER
+
ENV APP_ENV=prod

# ...

RUN mv "$PHP_INI_DIR/php.ini-production" "$PHP_INI_DIR/php.ini"
composer dump-autoload --classmap-authoritative --no-dev; \
composer dump-env prod; \
composer run-script --no-dev post-install-cmd; \
- chmod +x bin/console; sync;
+ chmod +x bin/console; \
+ chown -R $PUID:$PGID var/; sync
+
+USER $USER
```

`compose.override.yaml`

```yaml
services:
php:
build:
context: .
target: frankenphp_dev
+ args:
+ PUID: ${PUID:-1000}
+ PGID: ${PGID:-1000}
+ user: "${PUID:-1000}:${PGID:-1000}"
```
`compose.prod.yaml`

```yaml
services:
php:
build:
context: .
target: frankenphp_prod
+ args:
+ PUID: ${PUID:-1000}
+ PGID: ${PGID:-1000}
+ user: "${PUID:-1000}:${PGID:-1000}"
```

## Usage

After applying the previous changes, you have to pass the `PUID` and `PGID` as environment variables to the Dockerfile.

You can do this in a myriad of different ways:

- Export your `PUID` and `PGID` to your current shell before running `docker compose`.

```console
export PUID=$(id -u); export PGID=$(id -g); docker compose ...
```

- Pass `PUID` and `PGID` directly to `docker compose`.

```console
PUID=$(id -u) PGID=$(id -g) docker compose ...
```

- Add `PUID` and `PGID` to your dotenv (`.env`) file.

```dotenv
PUID=1000
PGID=1000
```

> [!CAUTION]
> This method is not recommended as it can cause issues in CI environment where the runner has a different UID/GID.

- Use third-party tools, like [`Task`](https://taskfile.dev/), to do the heavy lifting for you.

```yaml
version: "3"
env:
PUID:
sh: id -u
PGID:
sh: id -g
tasks:
up:
desc: Up stack
cmds:
- docker compose ...
```