diff --git a/composer.json b/composer.json index 75b6fa585..be2651382 100644 --- a/composer.json +++ b/composer.json @@ -92,6 +92,7 @@ "wohali/oauth2-discord-new": "^1.2" }, "replace": { + "tempest/aloft": "self.version", "tempest/auth": "self.version", "tempest/cache": "self.version", "tempest/clock": "self.version", @@ -132,6 +133,7 @@ "prefer-stable": true, "autoload": { "psr-4": { + "Tempest\\Aloft\\": "packages/aloft/src", "Tempest\\Auth\\": "packages/auth/src", "Tempest\\Cache\\": "packages/cache/src", "Tempest\\Clock\\": "packages/clock/src", @@ -203,6 +205,7 @@ }, "autoload-dev": { "psr-4": { + "Tempest\\Aloft\\Tests\\": "packages/aloft/tests", "Tempest\\Auth\\Tests\\": "packages/auth/tests", "Tempest\\Cache\\Tests\\": "packages/cache/tests", "Tempest\\Clock\\Tests\\": "packages/clock/tests", @@ -276,4 +279,4 @@ "composer exceptions:build" ] } -} +} \ No newline at end of file diff --git a/docs/0-getting-started/03-docker.md b/docs/0-getting-started/03-docker.md new file mode 100644 index 000000000..2fd1304f1 --- /dev/null +++ b/docs/0-getting-started/03-docker.md @@ -0,0 +1,235 @@ +--- +title: Docker +description: Tempest can both be developed or deployed in Production, with our own Docker images. Or, copy the Dockerfiles and customise as you need with our utility commands. +--- + +## Overview + +We are pleased to offer TempestPHP/Aloft, our own set of Docker images for developing with and serving your Tempest applications, for you to use as and customise as you see fit. + +In order to start from the strongest security posture and enable you to run secure and performant Tempest-based applications, we've initially selected FrankenPHP as our server of choice. Further, we've adopted a 'rootless' approach by default, and also offer a 'distroless' production image to further mitigate potential security issues stemming from unnecessary software often found in Docker images. + +## Aloft image architecture, variants and release strategy + +Our CI/CD will automatically generate and publish images to our public repository at https://PLACE.HOLD.ER/tempestphp/aloft following the releases of PHP and FrankenPHP, and also any time we find an issue in the underlying Docker image. Alternatively, you can also customise these images for your own use, see section below. (TODO: link) + +### Architectures + +We publish `amd64` AKA `x86_64`, and `aarch64` AKA `arm64` releases, which should work on most Linux and MacOS host systems, as part of a multi-arch image. The appropriate version should be selected automatically for your host system by docker when providing the image. + +Our upstream providers have some support for other architectures, should you need to support other platforms; see Customising the Docker image for your use, below. + +### Variants and release strategy + +We maintain two variants; 'latest' which is rootless and distroless, and is aimed at your test, qa and production needs, and 'debug' which is the same base image, with busybox available in case you need to access the docker shell. + +```bash +# These periodically updated variant tags will always point at the latest version-pinned images +tempestphp/aloft >> tempestphp/aloft:1.11.3-8.5.3 #at time of writing +tempestphp/aloft:latest >> tempestphp/aloft:1.11.3-8.5.3 #at time of writing +tempestphp/aloft:debug >> tempestphp/aloft:1.11.3-8.5.3-debug #at time of writing + +# We'll also continually publish pinned-versions +tempestphp/aloft:1.11.3-8.5.3 +tempestphp/aloft:1.11.3-8.5.3-debug +# these will accumulate over time +``` +We utilise the [GoogleContainerTools Distroless](https://github.com/GoogleContainerTools/distroless/) [`cc`](https://github.com/GoogleContainerTools/distroless/blob/main/cc/README.md) image, pulling their latest 'nonroot' image as our base, at time of build. +```dockerfile +FROM gcr.io/distroless/cc-debian13:nonroot AS runner +``` + +### Response to security incidents in the software chain + +We monitor our three upstream providers, GoogleContainerTools, PHP, and FrankenPHP, for security defect announcements. + +- We will actively replace pinned-version images utilising an instance of the distroless base image found to have any security defects. +- We will actively retire pinned-version images utilising an instance of PHP or FrankenPHP releases found to have any security defects. + +:::info +We won't automatically retire 'patch' version releases i.e. PHP8.5.3 > PHP8.5.4, unless subject to security defects specifically, in which case we'll re-build the image and re-publish. We encourage you to monitor the upstreams and update regularly, or use the `:debug` or `:latest` releases where possible. +::: + +## Developing your application with Docker + +During development, we'd suggest using the debug image. We've included a convenience command in the `tempest/aloft` package which will run a development server on your device. +```bash +./tempest aloft:serve # by default, this will get debug from the repository and serve it +``` +You may specify the `latest` image if you prefer. +```bash +./tempest aloft:serve latest # latest floating version +``` +You may instead specify the release, if you require a pinned-version. +```bash +./tempest aloft:serve 1.11.3-8.5.3 # pinned-version, distroless +./tempest aloft:serve 1.11.3-8.5.3-debug # pinned-version, debug +``` + +:::info +By default, the `aloft:serve` command will try to pull from the registry. But if you have published the stub for customising the image, this command will attempt to use the local image. You can force this behaviour by adding the optional command `--repository=local` or `--repository=remote`. +::: + +## Testing and production applications with Docker + +For testing and QA, we'd suggest using the distroless image, as it is most representative of your final infrastructure, and should highlight any issues for your attention. +```bash +./tempest aloft:serve latest +``` +As per the section above, you can omit `latest` to default to the `debug` release, or specify a version. + +## Customising the Docker image for your use + +As the `latest` and `debug` images are inherently distroless, albeit with busybox in the `debug` image, you cannot use this as an intermediate stage in a multi-stage Dockerfile build. Instead, you can use the `aloft:publish` Tempest command to publish a copy of the stubs, so you can build and tweak as you need. +```bash +./tempest aloft:publish # by default, this will publish the debug dockerfile +./tempest aloft:publish:latest # select the distroless image, instead +``` +This will publish `.dockerignore`, `Caddyfile`, and `Dockerfile` into your project root `docker/` folder, creating it as necessary. If you already have files in here, it shouldn't overwrite by default. + +You can also retrieve the files manually, from the vendor folder. +```bash +vendor/tempest/framework/packages/aloft/stubs/ +``` +### Building the image + +We've provided a simple `aloft:build` command to build these local images. It won't handle all use cases, and is really only aimed at someone directly running the images. If you are ready to change the Dockerfile to suit your needs, you probably won't want to use this anyway. That said, here's how to use it. + +If you HAVE NOT published the stubs to your project: +```bash +./tempest aloft:build # will attempt to build debug directly from the package stubs folder +./tempest aloft:build debug # will attempt to build debug directly from the package stubs folder +./tempest aloft:build latest # will attempt to build distroless directly from the package stubs folder +``` +If you HAVE published the stubs to your project: +```bash +./tempest aloft:build # will attempt to build debug from `{root_path}/docker/` +./tempest aloft:build debug # will attempt to build debug from `{root_path}/docker/` +./tempest aloft:build latest # will attempt to build distroless from `{root_path}/docker/` +``` +:::info +If you've published both stubs, or renamed the Dockerfile, this won't work. You've moved past the use-case this command was designed for, and will need to build yourself. Or copy the AloftBuildCommand into your project and customise it to suit you! +::: + +### Default versions of FrankenPHP and PHP + +We will update the stubs from time-to-time, but you may find that your PHP and/or FrankenPHP versions are out of step, because you have customised your file and don't wish to republish the stubs losing the changes. + +You can use the `aloft:build` command to pass the arguments: +```bash +./tempest aloft:build {''|debug|latest} --with-frankenphp="1.11.3" --with-php="8.5.3" +``` +:::info +Note that this will tag the image with tempestphp/aloft:debug or :latest, and remains compatible with `aloft:serve`. +::: + +Or, you pass these via build arguments run from the `{root_path}/docker/` folder: +```bash +docker build . -t tempestphp/aloft:1.11.3-8.5.3 --build-arg FRANKENPHP_VERSION="1.11.3" --build-arg PHP_VERSION="8.5.3" +``` +:::info +To retain compatibility with `aloft:serve` ensure that the image retains `tempestphp/aloft:` and then pass `1.11.3-8.5.3` as the image variant i.e. `./tempest aloft:serve 1.11.3-8.5.3`. +::: + +Or you can edit the Dockerfile directly: +```bash +ARG FRANKENPHP_VERSION=1.11.3 +ARG PHP_VERSION=8.5.3 +``` +:::info +This method also retains compatibility with `aloft:serve` and `aloft:build`, as long as you keep the filename unchanged. +::: + +## Adding additional PHP Extensions + +We include PHP Extensions from [Marc Henderkes'](https://pkgs.henderkes.com/) Static PHP Repository. These are static builds, of PHP-ZTS, which is required by FrankenPHP. + +:::info +Note that apt-get packages are kebab-case and should be prefixed `php-zts`. So if you wanted the extension `pdo_mysql`, you'd specify `php-zts-pdo-mysql`. +::: + +### Adding extensions at build time via build arguments + +This method is useful if you need to make a specific build one-off, containing an additional extension. + +Pass the build argument directly if using a published stub Dockerfile: +```bash +docker build . -t aloft:with-yaml --build-arg PHP_EXTRA_EXTENSIONS="php-zts-yaml" +``` +Or, you can use the Tempest aloft:build command and pass the optional argument: +```bash +./tempest aloft:build --with-php-extensions="php-zts-yaml" +``` +:::info +This will work with both the `debug` and `latest` images. +::: + +### Adding extensions to the Dockerfile + +This method is useful if you want to make your own image which always includes + +```dockerfile +RUN + # cropped for brevity + apt-get install -y --download-only --no-install-recommends \ + ca-certificates \ + frankenphp \ + php-zts-gd \ + php-zts-intl \ + php-zts-mysqli \ + php-zts-pdo-mysql \ + php-zts-pdo-pgsql \ + php-zts-pdo-sqlite \ + php-zts-redis \ + # Insert additional extensions here, space separated, or one per line followed by 'space, slash' i.e. ' \' + php-zts-zip ${PHP_EXTRA_EXTENSIONS}; \ +``` + +## Composer + +We don't package composer in the image currently, mostly due to the lack of shell in the distroless image. While it could potentially be included in the debug image, the primary use for the debug image is likely to be a developer's local machine, with a volume mount for the app. The host itself will almost certainly have composer installed as part of the developer's IDE and toolset, meaning it's presence in debug is largely redundant and unlikely to be used commonly. It would also have unequal updates since we are not version-pinning composer, resulting in stale versions present in the docker volumes. + +We suggest one of the following options instead. + +### Run composer from docker composer:latest + +You can use the following command to run composer interactively: +```bash +docker run --rm -i --tty --volume $PWD:/app --user 1002:1002 composer:latest # We suggest running with 1002:1002 to match the file permissions within our rootless image +``` +You could also create an alias script: +```bash +sudo sh -c 'echo "#!/usr/bin/env sh\ndocker run --rm -it --volume \"\$PWD:/app\" --user 1002:1002 composer:latest composer \"\$@\"" > /usr/local/bin/composer' && sudo chmod +x /usr/local/bin/composer +``` +This would allow you to execute `composer install` from the command line, via docker, without it being installed on your host. + +:::info +You can find more detailed instructions for running composer via docker [here](https://github.com/docker-library/docs/tree/master/composer). +::: + +## Frequently asked questions + +### Why no Alpine image? + +FrankenPHP strongly recommends not using Alpine for production environments - see [Don't Use Musl on FrankenPHP docs](https://frankenphp.dev/docs/performance/#dont-use-musl) - due to performance loss under ZTS mode. We decided not to offer even a development image on Alpine, since your application should be developed on a comparable environment to ensure consistency throughout. + +One of the main benefits of Alpine is that it's typically considered 'distroless' being built up from an empty system, and smaller. We chose to address this by selecting a distroless Debian Trixie base image, which while not as small as Alpine, is still minimised and at time of writing approximately 263MB. + +:::info +For comparison, the FrankenPHP official images at time of writing come in at 182MB for Alpine and 613MB for Debian, neither of which include all the extensions we add. This means our 'distroless' image is actually very similar in size to the Alpine image. +::: + +### Why no install-php-extensions, PHPIZE, PIE etc? + +Put simply, the utilities (apt, deb, make, build-essentials, etc etc) required to install with these tools add a lot of bloat to the final image. They're only needed at build time, and potentially represent a security risk should we leave them within the final image. So, we recommend you do not install such utilities and instead use the provided mechanisms above to install from the provided repository. + +### Why aren't you using Sury's / other repository? + +Sury's PHP repository doesn't offer PHP-ZTS, and the Henderkes repository has the benefit of being officially-recognised by the FrankenPHP team; it is the repo in their documentation for apt-get / rpm / apk installs for FrankenPHP itself, and as a consequence also has the PHP-ZTS packages to link with it. + +The Henderkes repos are also static, which is significantly cleaner for a distroless image, as the only dependency they have is effectively just `gcc-base`. + +## More questions? + +- [Join the Discord server](https://tempestphp.com/discord) +- [Raise an issue on github](https://github.com/tempestphp/tempest-framework/issues) \ No newline at end of file diff --git a/packages/aloft/composer.json b/packages/aloft/composer.json new file mode 100644 index 000000000..686001f82 --- /dev/null +++ b/packages/aloft/composer.json @@ -0,0 +1,22 @@ +{ + "name": "tempest/aloft", + "description": "Development and Production webserver Dockerfiles and utilities for TempestPHP.", + "require": { + "php": "^8.5", + "tempest/core": "3.x-dev", + "tempest/support": "3.x-dev" + }, + "require-dev": {}, + "autoload": { + "psr-4": { + "Tempest\\Aloft\\": "src" + } + }, + "autoload-dev": { + "psr-4": { + "Tempest\\Aloft\\Tests\\": "tests" + } + }, + "license": "MIT", + "minimum-stability": "dev" +} \ No newline at end of file diff --git a/packages/aloft/src/AloftBuildCommand.php b/packages/aloft/src/AloftBuildCommand.php new file mode 100644 index 000000000..3e50bb832 --- /dev/null +++ b/packages/aloft/src/AloftBuildCommand.php @@ -0,0 +1,77 @@ +assumedVariant = match (true) { + exists("{$testPath}latest") => 'latest', + exists("{$testPath}debug") => 'debug', + default => null, + }; + + $this->stubsPublished = $this->assumedVariant !== null; + } + + #[ConsoleCommand( + name: 'aloft:build', + description: 'Build the Aloft Docker image locally, and publish the stub files if not already present.', + )] + public function build( + #[ConsoleArgument( + description: 'The build variant to use.', + )] + string $requestedVariant = '', + #[ConsoleArgument( + name: 'with-php-extensions', + description: 'Space-separated list of extra extensions to include in the build.', + )] + ?string $withPhpExtensions = null, + #[ConsoleArgument( + name: 'with-frankenphp', + description: 'FrankenPHP version to pass as a build ARG.', + )] + ?string $withFrankenphp = null, + #[ConsoleArgument( + name: 'with-php', + description: 'PHP version to pass as a build ARG.', + )] + ?string $withPhp = null, + ): void { + $variant = $requestedVariant === '' ? $this->assumedVariant ?? 'debug' : $requestedVariant; + + $buildArgs = implode('', array_filter([ + $withFrankenphp !== null ? " --build-arg FRANKENPHP_VERSION=\"{$withFrankenphp}\"" : null, + $withPhp !== null ? " --build-arg PHP_VERSION=\"{$withPhp}\"" : null, + $withPhpExtensions !== null ? " --build-arg PHP_EXTRA_EXTENSIONS=\"{$withPhpExtensions}\"" : null, + ])); + + $buildPath = ($this->stubsPublished ? root_path('docker') : dirname(__DIR__) . DIRECTORY_SEPARATOR . 'stubs') . DIRECTORY_SEPARATOR; + $buildFile = "{$buildPath}Dockerfile.{$variant} -t tempestphp/aloft:{$variant}"; + + if ($this->confirm("Do you want to build tempestphp/aloft:{$variant}?", default: false)) { + $this->console->info('Okay, attempting build'); + passthru("docker build -f {$buildFile}{$buildArgs} {$buildPath}"); + } + } +} diff --git a/packages/aloft/src/AloftPublishCommand.php b/packages/aloft/src/AloftPublishCommand.php new file mode 100644 index 000000000..4d3b7d5a0 --- /dev/null +++ b/packages/aloft/src/AloftPublishCommand.php @@ -0,0 +1,64 @@ + '.dockerignore', + 'Caddyfile' => 'Caddyfile', + "Dockerfile.{$variant}" => "Dockerfile.{$variant}", + ]) + ->each( + function (string $dstFile, string $srcFile) use ($srcPath, $dstPath) { + copy_file( + source: $srcPath . $srcFile, + destination: $dstPath . $dstFile, + ); + }, + ); + + // copy_file will throw a runtime exception if this fails, so write a success + $this->console->success("Stub files copied to {$dstPath}"); + } + + #[ConsoleCommand( + name: 'aloft:publish:latest', + description: 'Publish the Aloft Docker stubs, for the distroless image.', + aliases: ['aloft:publish:distroless', 'aloft:publish:prod'], + )] + public function publishLatest(): void + { + $this->publish('latest'); + } +} diff --git a/packages/aloft/src/AloftServeCommand.php b/packages/aloft/src/AloftServeCommand.php new file mode 100644 index 000000000..730b386f7 --- /dev/null +++ b/packages/aloft/src/AloftServeCommand.php @@ -0,0 +1,82 @@ +assumedVariant = match (true) { + exists("{$testPath}latest") => 'latest', + exists("{$testPath}debug") => 'debug', + default => null, + }; + + $this->stubsPublished = $this->assumedVariant !== null; + + $this->remotePath = 'PLACE.HOLD.ER/'; + } + + #[ConsoleCommand( + name: 'aloft:build', + description: 'Build the Aloft Docker image locally, and publish the stub files if not already present.', + )] + public function build( + #[ConsoleArgument( + description: 'The build variant to use.', + )] + string $requestedVariant = '', + #[ConsoleArgument( + name: 'repository', + description: 'Space-separated list of extra extensions to include in the build.', + )] + ?string $repository = null, + ): void { + $variant = $requestedVariant === '' ? $this->assumedVariant ?? 'debug' : $requestedVariant; + $repo = $repository ?? ($this->stubsPublished ? '' : $this->remotePath); + + // TODO: Catch local development paths from composer.json and insert them as volumes + + $runImage = "{$repo}tempestphp/aloft:{$variant}"; + + if ($this->confirm("Do you want to start dev server from {$runImage}?", default: true)) { + $this->console->info('Okay, starting, use ctrl-c to exit when finished'); + if ($this->stubsPublished === true && ! ($repository ?? null === 'remote')) { + $this->console->info('Stubs are published, and you are using the local repository, therefore ensure that you run aloft:build before using aloft:serve'); + } + passthru( + "docker run --rm -it -p 80:8000 -p 443:8443 -p 443:8443/udp \ + -v " + . root_path() + . ":/app \ + -v " + . root_path('.frankenpest/data') + . ":/data \ + -v " + . root_path('.frankenpest/config') + . ":/config \ + {$runImage}", + ); + } + } +} diff --git a/packages/aloft/stubs/.dockerignore b/packages/aloft/stubs/.dockerignore new file mode 100644 index 000000000..27bf69a9d --- /dev/null +++ b/packages/aloft/stubs/.dockerignore @@ -0,0 +1,66 @@ +# ----------------------------------------------------------------------- +# Git +# ----------------------------------------------------------------------- +.git +.gitignore +.gitattributes + +# ----------------------------------------------------------------------- +# Docker build tooling (never needed inside the image) +# ----------------------------------------------------------------------- +Dockerfile +docker-bake.hcl +.dockerignore + +# ----------------------------------------------------------------------- +# CI / tooling config +# ----------------------------------------------------------------------- +.github +.gitlab-ci.yml +.travis.yml +.editorconfig +.env* +!.env.example + +# ----------------------------------------------------------------------- +# PHP tooling and dev dependencies +# ----------------------------------------------------------------------- +/vendor +composer.lock + +# ----------------------------------------------------------------------- +# Node (if any frontend tooling is present) +# ----------------------------------------------------------------------- +node_modules +npm-debug.log +yarn-error.log + +# ----------------------------------------------------------------------- +# Tests +# ----------------------------------------------------------------------- +/tests +/test +phpunit.xml +phpunit.xml.dist +.phpunit.result.cache +.phpunit.cache + +# ----------------------------------------------------------------------- +# Static analysis and code style +# ----------------------------------------------------------------------- +.php-cs-fixer.cache +.php-cs-fixer.php +phpstan.neon +phpstan.neon.dist +psalm.xml +psalm.xml.dist + +# ----------------------------------------------------------------------- +# IDE and OS noise +# ----------------------------------------------------------------------- +.idea +.vscode +*.swp +*.swo +.DS_Store +Thumbs.db diff --git a/packages/aloft/stubs/Caddyfile b/packages/aloft/stubs/Caddyfile new file mode 100644 index 000000000..71f846e85 --- /dev/null +++ b/packages/aloft/stubs/Caddyfile @@ -0,0 +1,56 @@ +# The Caddyfile is an easy way to configure FrankenPHP and the Caddy web server. +# This Caddyfile is provided by the TempestPHP Framework with some added options for convenience. +# +# https://github.com/tempestphp/tempest-framework +# https://frankenphp.dev/docs/config +# https://caddyserver.com/docs/caddyfile + +{ + skip_install_trust + {$CADDY_DEFAULT_BIND} + http_port {$CADDY_HTTP_PORT:8000} + https_port {$CADDY_HTTPS_PORT:8443} + + {$CADDY_GLOBAL_OPTIONS} + + frankenphp { + {$FRANKENPHP_CONFIG} + } +} + +{$CADDY_EXTRA_CONFIG} + +{$CADDY_SERVER_NAME:localhost} { + #log { + # # Redact the authorization query parameter that can be set by Mercure + # format filter { + # request>uri query { + # replace authorization REDACTED + # } + # } + #} + + root {$CADDY_SERVER_ROOT:public/} + encode zstd br gzip + + # Uncomment the following lines to enable Mercure and Vulcain modules + #mercure { + # # Publisher JWT key + # publisher_jwt {env.MERCURE_PUBLISHER_JWT_KEY} {env.MERCURE_PUBLISHER_JWT_ALG} + # # Subscriber JWT key + # subscriber_jwt {env.MERCURE_SUBSCRIBER_JWT_KEY} {env.MERCURE_SUBSCRIBER_JWT_ALG} + # # Allow anonymous subscribers (double-check that it's what you want) + # anonymous + # # Enable the subscription API (double-check that it's what you want) + # subscriptions + # # Extra directives + # {$MERCURE_EXTRA_DIRECTIVES} + #} + #vulcain + + {$CADDY_SERVER_EXTRA_DIRECTIVES} + + php_server { + #worker /path/to/your/worker.php + } +} \ No newline at end of file diff --git a/packages/aloft/stubs/Dockerfile.debug b/packages/aloft/stubs/Dockerfile.debug new file mode 100644 index 000000000..abd546849 --- /dev/null +++ b/packages/aloft/stubs/Dockerfile.debug @@ -0,0 +1,108 @@ +ARG FRANKENPHP_VERSION=1.11.3 +ARG PHP_VERSION=8.5.3 +ARG PHP_EXTRA_EXTENSIONS=" " + +FROM debian:trixie-slim AS builder +ARG FRANKENPHP_VERSION +ARG PHP_VERSION +ARG PHP_EXTRA_EXTENSIONS + +RUN set -eux; \ + CHROOT=/chroot; \ + \ + { \ + echo 'Package: php*'; \ + echo "Pin: version ${PHP_VERSION}*"; \ + echo 'Pin-Priority: 1001'; \ + echo ''; \ + echo 'Package: frankenphp'; \ + echo "Pin: version ${FRANKENPHP_VERSION}+php85*"; \ + echo 'Pin-Priority: 1001'; \ + echo ''; \ + echo 'Package: php*'; \ + echo 'Pin: release o=Debian'; \ + echo 'Pin-Priority: -1'; \ + } > /etc/apt/preferences.d/static-php85; \ + apt-get update; \ + apt-get install -y --no-install-recommends busybox ca-certificates curl mailcap xz-utils; \ + curl https://pkg.henderkes.com/api/packages/85/debian/repository.key -o /etc/apt/keyrings/static-php85.asc; \ + echo "deb [signed-by=/etc/apt/keyrings/static-php85.asc] https://pkg.henderkes.com/api/packages/85/debian php-zts main" > /etc/apt/sources.list.d/static-php85.list; \ + apt-get update; \ + apt-get install -y --download-only --no-install-recommends \ + ca-certificates \ + frankenphp \ + php-zts-gd \ + php-zts-intl \ + php-zts-mysqli \ + php-zts-pdo-mysql \ + php-zts-pdo-pgsql \ + php-zts-pdo-sqlite \ + php-zts-redis \ + php-zts-zip ${PHP_EXTRA_EXTENSIONS}; \ + mkdir -p $CHROOT; \ + for deb in /var/cache/apt/archives/*.deb; do \ + dpkg-deb --extract "$deb" $CHROOT; \ + done; \ + cp /etc/mime.types $CHROOT/etc/mime.types; \ + \ + # Download static curl into the chroot + case "$(dpkg --print-architecture)" in \ + amd64) CURL_ARCH="x86_64" ;; \ + arm64) CURL_ARCH="aarch64" ;; \ + armhf) CURL_ARCH="armv7" ;; \ + armel) CURL_ARCH="armv5" ;; \ + i386) CURL_ARCH="i686" ;; \ + *) echo "Unsupported arch: $(dpkg --print-architecture)" && exit 1 ;; \ + esac; \ + CURL_VERSION=$(curl -fsSL "https://api.github.com/repos/stunnel/static-curl/releases/latest" \ + | grep '"tag_name"' | head -1 \ + | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/'); \ + curl -fsSL -o /tmp/curl.tar.xz \ + "https://github.com/stunnel/static-curl/releases/download/${CURL_VERSION}/curl-linux-${CURL_ARCH}-glibc-${CURL_VERSION}.tar.xz"; \ + tar -xf /tmp/curl.tar.xz -C $CHROOT/usr/bin curl; \ + chmod +x $CHROOT/usr/bin/curl; \ + tar -C $CHROOT -cf /tmp/chroot.tar .; + +FROM gcr.io/distroless/cc-debian13:nonroot AS runner + +USER root + +COPY --from=builder /bin/busybox /usr/bin/busybox + +RUN --mount=type=bind,from=builder,source=/tmp/chroot.tar,target=/tmp/chroot.tar \ + ["/usr/bin/busybox", "sh", "-c", "\ + /usr/bin/busybox --install -s /usr/bin; \ + tar -xf /tmp/chroot.tar -C /; \ + mkdir -p \ + /app/public \ + /data/caddy \ + /config/caddy \ + /etc/frankenphp; \ + chown -R nonroot /app/public /data /config /etc/frankenphp;"] + +COPY --chown=nonroot Caddyfile /etc/frankenphp/Caddyfile + +# See https://caddyserver.com/docs/conventions#file-locations for details +ENV XDG_CONFIG_HOME=/config +ENV XDG_DATA_HOME=/data + +# Required from frankenphp +ENV GODEBUG=cgocheck=0 + +LABEL org.opencontainers.image.title=TempestPHP +LABEL org.opencontainers.image.description="The framework that gets out of your way" +LABEL org.opencontainers.image.url=https://tempestphp.com +LABEL org.opencontainers.image.source=https://github.com/tempestphp/tempest-framework/ +LABEL org.opencontainers.image.licenses=MIT +LABEL org.opencontainers.image.vendor="Brent Roose and contributors" + +WORKDIR /app + +EXPOSE 8000 +EXPOSE 8443 +EXPOSE 8443/udp + +USER nonroot + +CMD ["frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"] +HEALTHCHECK CMD curl -f http://localhost:2019/metrics || exit 1 diff --git a/packages/aloft/stubs/Dockerfile.latest b/packages/aloft/stubs/Dockerfile.latest new file mode 100644 index 000000000..baf5f4868 --- /dev/null +++ b/packages/aloft/stubs/Dockerfile.latest @@ -0,0 +1,106 @@ +ARG FRANKENPHP_VERSION=1.11.3 +ARG PHP_VERSION=8.5.3 +ARG PHP_EXTRA_EXTENSIONS=" " + +FROM debian:trixie-slim AS builder +ARG FRANKENPHP_VERSION +ARG PHP_VERSION +ARG PHP_EXTRA_EXTENSIONS + +RUN set -eux; \ + CHROOT=/chroot; \ + \ + { \ + echo 'Package: php*'; \ + echo "Pin: version ${PHP_VERSION}*"; \ + echo 'Pin-Priority: 1001'; \ + echo ''; \ + echo 'Package: frankenphp'; \ + echo "Pin: version ${FRANKENPHP_VERSION}+php85*"; \ + echo 'Pin-Priority: 1001'; \ + echo ''; \ + echo 'Package: php*'; \ + echo 'Pin: release o=Debian'; \ + echo 'Pin-Priority: -1'; \ + } > /etc/apt/preferences.d/static-php85; \ + apt-get update; \ + apt-get install -y --no-install-recommends busybox ca-certificates curl mailcap xz-utils; \ + curl https://pkg.henderkes.com/api/packages/85/debian/repository.key -o /etc/apt/keyrings/static-php85.asc; \ + echo "deb [signed-by=/etc/apt/keyrings/static-php85.asc] https://pkg.henderkes.com/api/packages/85/debian php-zts main" > /etc/apt/sources.list.d/static-php85.list; \ + apt-get update; \ + apt-get install -y --download-only --no-install-recommends \ + ca-certificates \ + frankenphp \ + php-zts-gd \ + php-zts-intl \ + php-zts-mysqli \ + php-zts-pdo-mysql \ + php-zts-pdo-pgsql \ + php-zts-pdo-sqlite \ + php-zts-redis \ + php-zts-zip ${PHP_EXTRA_EXTENSIONS}; \ + mkdir -p $CHROOT; \ + for deb in /var/cache/apt/archives/*.deb; do \ + dpkg-deb --extract "$deb" $CHROOT; \ + done; \ + cp /etc/mime.types $CHROOT/etc/mime.types; \ + \ + # Download static curl into the chroot + case "$(dpkg --print-architecture)" in \ + amd64) CURL_ARCH="x86_64" ;; \ + arm64) CURL_ARCH="aarch64" ;; \ + armhf) CURL_ARCH="armv7" ;; \ + armel) CURL_ARCH="armv5" ;; \ + i386) CURL_ARCH="i686" ;; \ + *) echo "Unsupported arch: $(dpkg --print-architecture)" && exit 1 ;; \ + esac; \ + CURL_VERSION=$(curl -fsSL "https://api.github.com/repos/stunnel/static-curl/releases/latest" \ + | grep '"tag_name"' | head -1 \ + | sed 's/.*"tag_name": *"\([^"]*\)".*/\1/'); \ + curl -fsSL -o /tmp/curl.tar.xz \ + "https://github.com/stunnel/static-curl/releases/download/${CURL_VERSION}/curl-linux-${CURL_ARCH}-glibc-${CURL_VERSION}.tar.xz"; \ + tar -xf /tmp/curl.tar.xz -C $CHROOT/usr/bin curl; \ + chmod +x $CHROOT/usr/bin/curl; \ + tar -C $CHROOT -cf /tmp/chroot.tar .; + +FROM gcr.io/distroless/cc-debian13:nonroot AS runner + +USER root + +RUN --mount=type=bind,from=builder,source=/tmp/chroot.tar,target=/tmp/chroot.tar \ + --mount=type=bind,from=builder,source=/bin/busybox,target=/bin/busybox \ + ["/bin/busybox", "sh", "-c", "\ + /bin/busybox tar -xf /tmp/chroot.tar -C /; \ + /bin/busybox mkdir -p \ + /app/public \ + /data/caddy \ + /config/caddy \ + /etc/frankenphp; \ + /bin/busybox chown -R nonroot /app/public /data /config /etc/frankenphp;"] + +COPY --chown=nonroot Caddyfile /etc/frankenphp/Caddyfile + +# See https://caddyserver.com/docs/conventions#file-locations for details +ENV XDG_CONFIG_HOME=/config +ENV XDG_DATA_HOME=/data + +# Required from frankenphp +ENV GODEBUG=cgocheck=0 + +LABEL org.opencontainers.image.title=TempestPHP +LABEL org.opencontainers.image.description="The framework that gets out of your way" +LABEL org.opencontainers.image.url=https://tempestphp.com +LABEL org.opencontainers.image.source=https://github.com/tempestphp/tempest-framework/ +LABEL org.opencontainers.image.licenses=MIT +LABEL org.opencontainers.image.vendor="Brent Roose and contributors" + +WORKDIR /app + +EXPOSE 8000 +EXPOSE 8443 +EXPOSE 8443/udp + +USER nonroot + +CMD ["frankenphp", "run", "--config", "/etc/frankenphp/Caddyfile", "--adapter", "caddyfile"] +HEALTHCHECK CMD curl -f http://localhost:2019/metrics || exit 1 diff --git a/packages/aloft/tests/tests.txt b/packages/aloft/tests/tests.txt new file mode 100644 index 000000000..e1a7747d7 --- /dev/null +++ b/packages/aloft/tests/tests.txt @@ -0,0 +1 @@ +Tests are TBC as it may not be appropriate to use PHPUnit to test here \ No newline at end of file