diff --git a/content/chainguard/chainguard-images/getting-started/php.md b/content/chainguard/chainguard-images/getting-started/php.md index 09e1a25574..93abc6cc38 100644 --- a/content/chainguard/chainguard-images/getting-started/php.md +++ b/content/chainguard/chainguard-images/getting-started/php.md @@ -17,17 +17,17 @@ weight: 045 toc: true --- -The PHP images maintained by Chainguard are a mix of development and production distroless images that are suitable for building and running PHP workloads. +The PHP images maintained by Chainguard include both development and production container images, suitable for building and running PHP workloads. The `latest-fpm` variant serves PHP applications over FastCGI, while the `latest` variant runs PHP applications from the command line. -Because PHP applications typically require the installation of third-party dependencies via Composer, using a pure distroless image for building your application would not work. In cases like this, you'll need to implement a [multi-stage Docker build](https://docs.docker.com/build/building/multi-stage/) that uses one of the `-dev` images to set up the application. +In this guide, we'll set up a demo and demonstrate how you can use Chainguard Images to develop, build, and run PHP applications. -In this guide, we'll set up a distroless container image based on Wolfi as a runtime to execute a command-line PHP application. +This tutorial requires Docker to be installed on your local machine. If you don't have Docker installed, you can download and install it from the [official Docker website](https://docs.docker.com/get-docker/). -{{< details "What is distroless" >}} +{{< details "What is distroless?" >}} {{< blurb/distroless >}} {{< /details >}} -{{< details "What is Wolfi" >}} +{{< details "What is Wolfi?" >}} {{< blurb/wolfi >}} {{< /details >}} @@ -35,31 +35,43 @@ In this guide, we'll set up a distroless container image based on Wolfi as a run {{< blurb/images >}} {{< /details >}} -## Step 1: Setting up a Demo Application +## 1. Setting up a (CLI) Demo Application -We'll start by creating a basic command-line PHP application to serve as a demo. This app will generate random names based on a list of nouns and adjectives. To exemplify usage with Composer, the app will have a single dependency on [minicli](https://github.com/minicli/minicli), a minimalist CLI framework for PHP. +We'll start by getting the demo application ready. This CLI app generates random names based on a list of nouns and adjectives. To exemplify usage with Composer, the app has a single dependency on [minicli](https://github.com/minicli/minicli), a minimalist CLI framework for PHP. -First, create a directory for your app. In this guide we'll use `wolfi-php`: +Start by cloning the demos repository to your local machine: ```shell -mkdir ~/wolfi-php && cd ~/wolfi-php +git clone git@github.com:chainguard-dev/edu-images-demos.git ``` -If you have a local PHP development environment with Composer, you can run the following command to download the single dependency of this app. +Locate the `namegen` demo and cd into its directory: -``` -composer require minicli/minicli +```shell +cd edu-images-demos/php/namegen ``` -If you don't have a local PHP development environment, you can use the `php:latest-dev` image variant with a volume in order to install application dependencies with Composer: +You can use the `php:latest-dev` image variant with a volume in order to install application dependencies with Composer. We'll use the `root` user to be able to write to the volume mounted in the container: ```shell -docker run --rm -v ${PWD}:/work --entrypoint composer --user root \ +docker run --rm -v ${PWD}:/app --entrypoint composer --user root \ cgr.dev/chainguard/php:latest-dev \ - require minicli/minicli --working-dir=/work + install --no-progress --no-dev --prefer-dist ``` -If you used the Docker method, make sure permissions are set correctly on the generated files. +You'll get output like this, indicating that the package `minicli/minicli` was installed: + +```shell +Installing dependencies from lock file +Verifying lock file contents can be installed on current platform. +Package operations: 1 install, 0 updates, 0 removals + - Downloading minicli/minicli (4.2.0) + - Installing minicli/minicli (4.2.0): Extracting archive +Generating autoload files +1 package you are using is looking for funding. +Use the `composer fund` command to find out more! +``` +Next, make sure permissions are set correctly on the generated files. On Linux systems run the following: @@ -73,19 +85,7 @@ On macOS systems, run this: sudo chown -R ${USER} . ``` -Create a new file to serve as the application entry point. We'll call it `namegen`: - -```shell -touch namegen -``` - -Next, open the file in your code editor of choice, for example with `nano`: - -```shell -nano namegen -``` - -The following PHP script defines a minimalist CLI app with a single command called `get`. It returns a random name based on a list of nouns and a list of adjectives. +The application should now be ready to be executed. For transparency, here is the code that will be executed, which you'll find in the `namegen` script: ```php #!/usr/bin/php @@ -109,19 +109,7 @@ $app->runCommand($argv); ``` -Copy this content to your `namegen` script, save and close the file. Next, make the script executable with: - -```shell -chmod +x namegen -``` - -You can now execute the app to test it out. If you have `php` locally, run: - -```shell -php namegen get -``` - -If you're using the Docker setup, run: +You can now execute the app to test it out. Run the following command: ```shell docker run --rm -v ${PWD}:/work \ @@ -134,35 +122,18 @@ The command should output a random name combination: ```shell ludicrous-walrus ``` +In the next step, you'll build the application in a multi-stage Dockerfile. -The demo application is now ready. In the next step, you'll create a Dockerfile to run your app. - -## Step 2: Creating the Dockerfile +## 2. Building a Distroless Image for the Application -To make sure our final image is _distroless_ while still being able to install dependencies with Composer, our build will consist of **two** stages: first, we'll build the application using the `dev` image variant, a Wolfi-based image that includes Composer and other useful tools for development. -Then, we'll create a separate stage for the final image. The resulting image will be based on the distroless PHP Wolfi image, which means it doesn't come with Composer or even a shell. +We'll now build a distroless image for the application. To be able to install dependencies with Composer, our build will consist of **two** stages: first, we'll build the application using the `dev` image variant, a Wolfi-based image that includes Composer and other useful tools for development. Then, we'll create a separate stage for the final image. The resulting image will be based on the distroless PHP Wolfi image, which means it doesn't come with Composer or even a shell. -Create a Dockerfile with: - -```shell -touch Dockerfile -``` - -Then open this file in your code editor of choice, for example `nano`: +Open the `Dockerfile` included within the root of the application folder. You can use your code editor of choice, for example `nano`: ```shell nano Dockerfile ``` -The following Dockerfile will: - -1. Start a new build stage based on the `php:latest-dev` image and call it `builder`; -2. Copy files from the current directory to the `/app` location in the container; -3. Enter the `/app` directory and run `composer install` to install any dependencies; -4. Start a new build stage based on the `php:latest` image; -5. Copy the application from the `builder` stage; -6. Set up the application as entry point for this image. - -Copy this content to your own `Dockerfile`: +For convenience, here is the content of the included `Dockerfile`: ```Dockerfile FROM cgr.dev/chainguard/php:latest-dev AS builder @@ -178,14 +149,43 @@ COPY --from=builder /app /app ENTRYPOINT [ "php", "/app/namegen" ] ``` -Save the file when you're finished. +This Dockerfile will: + +1. Start a new build stage based on the `php:latest-dev` image and call it `builder`; +2. Copy files from the current directory to the `/app` location in the container; +3. Enter the `/app` directory and run `composer install` to install any dependencies; +4. Start a new build stage based on the `php:latest` image; +5. Copy the application from the `builder` stage; +6. Set up the application as entry point for this image. You can now build the image with: ```shell docker build . -t php-namegen ``` +You'll get output similar to this: +```shell +[+] Building 0.1s (12/12) FINISHED docker:default + => [internal] load build definition from Dockerfile 0.0s + => => transferring dockerfile: 322B 0.0s + => [internal] load metadata for cgr.dev/chainguard/php:latest-dev 0.0s + => [internal] load metadata for cgr.dev/chainguard/php:latest 0.0s + => [internal] load .dockerignore 0.0s + => => transferring context: 2B 0.0s + => [internal] load build context 0.0s + => => transferring context: 4.86kB 0.0s + => [builder 1/4] FROM cgr.dev/chainguard/php:latest-dev 0.0s + => [stage-1 1/2] FROM cgr.dev/chainguard/php:latest 0.0s + => CACHED [builder 2/4] COPY . /app 0.0s + => CACHED [builder 3/4] RUN chown -R php /app 0.0s + => CACHED [builder 4/4] RUN cd /app && composer install --no-progress --no-dev --prefer-dist 0.0s + => CACHED [stage-1 2/2] COPY --from=builder /app /app 0.0s + => exporting to image 0.0s + => => exporting layers 0.0s + => => writing image sha256:e617d7afd472d4a78d82060eaacd3a1c33310d6a267f6aaf9aa34b44e3ef8e5c 0.0s + => => naming to docker.io/library/php-namegen 0.0s +``` Once the build is finished, run the image with: ```shell @@ -208,12 +208,12 @@ docker image inspect php-namegen "RootFS": { "Type": "layers", "Layers": [ - "sha256:23a50695d43b8aea7720c05bff1bdbfbcb45d0b0c7e7387f55d82110084002eb", - "sha256:9b900cbd280a3d510588c3b14bc937718ccee43a10b8b7b1756438b030bc3e15" + "sha256:52cf795862535c5f22dac055428527508088becebbe00293457693a5f8fa1df2", + "sha256:95a8dc6d81c92158ac032e5167768a04e45f25b3bf4009c5698673c19d36d5c2" ] }, "Metadata": { - "LastTagTime": "2023-01-10T19:02:13.062958609+01:00" + "LastTagTime": "2024-11-15T12:52:18.412117879+01:00" } } ] @@ -223,6 +223,111 @@ In such cases, the last `FROM` section from the Dockerfile is the one that compo It's worth highlighting that nothing is carried from one stage to the other unless you copy it. That facilitates creating a slim final image with only what's necessary to execute the application. +## 3. Working with the PHP-FPM Image Variant + +The `latest-fpm` image variant is suitable for running PHP applications over FastCGI, to be server via a web server such as Nginx. In this section, we'll run a Docker Compose setup using the `latest-fpm` image variant and the Chainguard Nginx image. + +The `namegen-api` demo is a variation of the previous demo, but it serves the random name generation over HTTP. The application responds with a JSON payload containing the animal and adjective combination, and accepts an optional `animal` parameter to specify the animal for the final suggested name. + +Start by accessing the `namegen-api` demo folder: + +```shell +cd edu-images-demos/php/namegen-api +``` + +The `index.php` file contains the following code: + +```php + $chosenAnimal, 'adjective' => $chosenAdjective]); +``` + +To serve this application over HTTP, we'll use the `latest-fpm` image variant in a Docker Compose setup. The following `docker-compose.yml` is included within the `namegen-api` folder: + +```yaml +services: + app: + image: cgr.dev/chainguard/php:latest-fpm + restart: unless-stopped + working_dir: /app + volumes: + - ./:/app + + nginx: + image: cgr.dev/chainguard/nginx + restart: unless-stopped + ports: + - 8000:80 + volumes: + - ./:/app + - ./nginx.conf:/etc/nginx/nginx.conf +``` + +This Docker Compose setup defines two services: `app` and `nginx`. The `app` service uses the `latest-fpm` image variant and mounts the current directory to the `/app` directory in the container. The `nginx` service uses the Chainguard Nginx image and also mounts the current directory to the `/app` directory in the container, setting it as the document root with a custom configuration. A second volume replaces the default Nginx configuration with our custom one, included as `nginx.conf` in the same directory: + +``` +events { + worker_connections 1024; +} + +http { + server { + listen 80; + index index.php index.html; + root /app/public; + location ~ \.php$ { + try_files $uri =404; + fastcgi_split_path_info ^(.+\.php)(/.+)$; + fastcgi_pass app:9000; + fastcgi_index index.php; + include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param PATH_INFO $fastcgi_path_info; + } + location / { + try_files $uri $uri/ /index.php?$query_string; + gzip_static on; + } + } +} +``` + +To run this setup, execute: + +```shell +docker-compose up +``` + +This command will start the services defined in the `docker-compose.yml` file. You can access the application at `http://localhost:8000` in your browser, or you can make a curl request to it: + +```shell +curl http://localhost:8000 +``` +You should get a JSON response with a random name combination: + +``` +{"animal":"octopus","adjective":"ludicrous"} +``` + +You can also try passing an `animal` parameter to the URL: + +```shell +curl 'http://localhost:8000?animal=cat' +``` + +``` +{"animal":"cat","adjective":"mischievous"} +``` + +To stop the services, you can press `Ctrl+C` in the terminal where you ran `docker-compose up`. + ## Advanced Usage {{< blurb/images-advanced image="PHP" >}}