DEV Community

Cristian Castaño
Cristian Castaño

Posted on

How to Use IMAP Extension with PHP 8.4 on AWS Lambda using Bref

How to Use IMAP Extension with PHP 8.4 on AWS Lambda using Bref

If you've ever tried to use the IMAP extension with PHP 8.4 on AWS Lambda using Bref, you've probably hit a wall. The IMAP extension isn't available as a pre-built layer for PHP 8.4, and compiling PHP extensions for Lambda's Amazon Linux 2 environment can be tricky.

In this article, I'll show you how I solved this problem using Docker multi-stage builds to compile the IMAP extension and deploy it with Bref.

Table of Contents

The Problem

Bref provides excellent PHP runtimes for AWS Lambda, including many common extensions through bref/extra-php-extensions. However, the IMAP extension for PHP 8.4 isn't available as a pre-built layer.

Why? The IMAP extension has several system-level dependencies:

  • c-client library (uw-imap)
  • Kerberos libraries
  • OpenSSL development files

These dependencies need to be compiled specifically for Amazon Linux 2, which is Lambda's underlying operating system.

The Solution: Docker Multi-Stage Builds

Instead of using Lambda layers, we'll use Docker container images for our Lambda functions. This approach gives us full control over the environment and allows us to:

  1. Compile the IMAP extension in a build stage using Bref's build image
  2. Copy the compiled extension to the final runtime image
  3. Deploy as container images to AWS ECR

The key insight is that Bref provides bref/build-php-84 - a Docker image specifically designed for compiling PHP extensions that will work on Lambda.

Understanding the Dockerfile

Let's break down what each part of the Dockerfile does:

Stage 1: Build the IMAP Extension

FROM bref/build-php-84:2 AS ext
Enter fullscreen mode Exit fullscreen mode

This uses Bref's build image, which contains all the tools needed to compile PHP extensions for Lambda's environment.

RUN LD_LIBRARY_PATH=/lib:/lib64 yum -y install amazon-linux-extras
RUN LD_LIBRARY_PATH=/lib:/lib64 amazon-linux-extras install epel -y
RUN LD_LIBRARY_PATH=/lib:/lib64 yum -y install uw-imap-devel krb5-devel openssl-devel libc-client-devel
Enter fullscreen mode Exit fullscreen mode

Here we install the IMAP dependencies. The LD_LIBRARY_PATH is necessary because of how libraries are organized in the Bref build image.

RUN pecl download imap-1.0.3 && tar -xzf imap-1.0.3.tgz
Enter fullscreen mode Exit fullscreen mode

We download the IMAP extension from PECL. Note that we use version 1.0.3 which is compatible with PHP 8.4.

RUN LD_LIBRARY_PATH=/lib64:/lib phpize \
    && LD_LIBRARY_PATH=/lib64:/lib ./configure \
        --with-imap \
        --with-kerberos \
        --with-imap-ssl=/usr \
        --with-libdir=lib64 \
        CFLAGS="-I/usr/include" \
        LDFLAGS="-L/lib64" \
    && LD_LIBRARY_PATH=/lib64:/lib make -j$(nproc) \
    && make install
Enter fullscreen mode Exit fullscreen mode

This compiles the extension with:

  • IMAP support
  • Kerberos authentication support
  • SSL/TLS support for secure IMAP connections
RUN cp $(php-config --extension-dir)/imap.so /tmp/imap.so \
    && echo 'extension=imap.so' > /tmp/ext-imap.ini \
    && mkdir -p /tmp/extension-libs \
    && php /bref/lib-copy/copy-dependencies.php /tmp/imap.so /tmp/extension-libs || true \
    && rm -f /tmp/extension-libs/libssl.so.3 /tmp/extension-libs/libcrypto.so.3 \
    && rm -f /tmp/extension-libs/libz.so.1
Enter fullscreen mode Exit fullscreen mode

This is crucial! We:

  1. Copy the compiled imap.so extension
  2. Create a PHP ini file to load it
  3. Use Bref's copy-dependencies.php to find and copy all required shared libraries
  4. Remove libraries that are already present in the runtime (to avoid conflicts)

Stage 2: The Runtime Image

The second stage uses the appropriate Bref runtime image and copies our compiled extension.

Complete Dockerfiles

Here are the complete Dockerfiles for different Lambda function types:

Dockerfile.web (for HTTP requests via API Gateway)

FROM bref/build-php-84:2 AS ext

RUN LD_LIBRARY_PATH=/lib:/lib64 yum -y install amazon-linux-extras
RUN LD_LIBRARY_PATH=/lib:/lib64 amazon-linux-extras install epel -y
RUN LD_LIBRARY_PATH=/lib:/lib64 yum -y install uw-imap-devel krb5-devel openssl-devel libc-client-devel

WORKDIR /tmp

RUN pecl download imap-1.0.3 && tar -xzf imap-1.0.3.tgz

WORKDIR /tmp/imap-1.0.3

RUN LD_LIBRARY_PATH=/lib64:/lib phpize \
    && LD_LIBRARY_PATH=/lib64:/lib ./configure \
        --with-imap \
        --with-kerberos \
        --with-imap-ssl=/usr \
        --with-libdir=lib64 \
        CFLAGS="-I/usr/include" \
        LDFLAGS="-L/lib64" \
    && LD_LIBRARY_PATH=/lib64:/lib make -j$(nproc) \
    && make install

RUN cp $(php-config --extension-dir)/imap.so /tmp/imap.so \
    && echo 'extension=imap.so' > /tmp/ext-imap.ini \
    && mkdir -p /tmp/extension-libs \
    && php /bref/lib-copy/copy-dependencies.php /tmp/imap.so /tmp/extension-libs || true \
    && rm -f /tmp/extension-libs/libssl.so.3 /tmp/extension-libs/libcrypto.so.3 \
    && rm -f /tmp/extension-libs/libz.so.1

FROM bref/php-84-fpm:2

# Copy the IMAP extension and its dependencies
COPY --from=ext /tmp/imap.so /opt/bref/extensions/imap.so
COPY --from=ext /tmp/ext-imap.ini /opt/bref/etc/php/conf.d/ext-imap.ini
COPY --from=ext /tmp/extension-libs/ /opt/lib/

# Add other extensions from bref/extra-php-extensions if needed
COPY --from=bref/extra-redis-php-84:1 /opt /opt/
COPY --from=bref/extra-ssh2-php-84:1 /opt /opt/

# Copy your application code
COPY . /var/task

CMD ["public/index.php"]
Enter fullscreen mode Exit fullscreen mode

Dockerfile.cli (for Artisan commands and scheduled tasks)

FROM bref/build-php-84:2 AS ext

RUN LD_LIBRARY_PATH=/lib:/lib64 yum -y install amazon-linux-extras
RUN LD_LIBRARY_PATH=/lib:/lib64 amazon-linux-extras install epel -y
RUN LD_LIBRARY_PATH=/lib:/lib64 yum -y install uw-imap-devel krb5-devel openssl-devel libc-client-devel

WORKDIR /tmp

RUN pecl download imap-1.0.3 && tar -xzf imap-1.0.3.tgz

WORKDIR /tmp/imap-1.0.3

RUN LD_LIBRARY_PATH=/lib64:/lib phpize \
    && LD_LIBRARY_PATH=/lib64:/lib ./configure \
        --with-imap \
        --with-kerberos \
        --with-imap-ssl=/usr \
        --with-libdir=lib64 \
        CFLAGS="-I/usr/include" \
        LDFLAGS="-L/lib64" \
    && LD_LIBRARY_PATH=/lib64:/lib make -j$(nproc) \
    && make install

RUN cp $(php-config --extension-dir)/imap.so /tmp/imap.so \
    && echo 'extension=imap.so' > /tmp/ext-imap.ini \
    && mkdir -p /tmp/extension-libs \
    && php /bref/lib-copy/copy-dependencies.php /tmp/imap.so /tmp/extension-libs || true \
    && rm -f /tmp/extension-libs/libssl.so.3 /tmp/extension-libs/libcrypto.so.3 \
    && rm -f /tmp/extension-libs/libz.so.1

FROM bref/php-84-console:2

# Copy the IMAP extension and its dependencies
COPY --from=ext /tmp/imap.so /opt/bref/extensions/imap.so
COPY --from=ext /tmp/ext-imap.ini /opt/bref/etc/php/conf.d/ext-imap.ini
COPY --from=ext /tmp/extension-libs/ /opt/lib/

# Add other extensions from bref/extra-php-extensions if needed
COPY --from=bref/extra-redis-php-84:1 /opt /opt/
COPY --from=bref/extra-ssh2-php-84:1 /opt /opt/

# Copy your application code
COPY . /var/task

CMD ["artisan"]
Enter fullscreen mode Exit fullscreen mode

Dockerfile.queue (for SQS queue workers)

FROM bref/build-php-84:2 AS ext

RUN LD_LIBRARY_PATH=/lib:/lib64 yum -y install amazon-linux-extras
RUN LD_LIBRARY_PATH=/lib:/lib64 amazon-linux-extras install epel -y
RUN LD_LIBRARY_PATH=/lib:/lib64 yum -y install uw-imap-devel krb5-devel openssl-devel libc-client-devel

WORKDIR /tmp

RUN pecl download imap-1.0.3 && tar -xzf imap-1.0.3.tgz

WORKDIR /tmp/imap-1.0.3

RUN LD_LIBRARY_PATH=/lib64:/lib phpize \
    && LD_LIBRARY_PATH=/lib64:/lib ./configure \
        --with-imap \
        --with-kerberos \
        --with-imap-ssl=/usr \
        --with-libdir=lib64 \
        CFLAGS="-I/usr/include" \
        LDFLAGS="-L/lib64" \
    && LD_LIBRARY_PATH=/lib64:/lib make -j$(nproc) \
    && make install

RUN cp $(php-config --extension-dir)/imap.so /tmp/imap.so \
    && echo 'extension=imap.so' > /tmp/ext-imap.ini \
    && mkdir -p /tmp/extension-libs \
    && php /bref/lib-copy/copy-dependencies.php /tmp/imap.so /tmp/extension-libs || true \
    && rm -f /tmp/extension-libs/libssl.so.3 /tmp/extension-libs/libcrypto.so.3 \
    && rm -f /tmp/extension-libs/libz.so.1

FROM bref/php-84:2

# Copy the IMAP extension and its dependencies
COPY --from=ext /tmp/imap.so /opt/bref/extensions/imap.so
COPY --from=ext /tmp/ext-imap.ini /opt/bref/etc/php/conf.d/ext-imap.ini
COPY --from=ext /tmp/extension-libs/ /opt/lib/

# Add other extensions from bref/extra-php-extensions if needed
COPY --from=bref/extra-redis-php-84:1 /opt /opt/
COPY --from=bref/extra-ssh2-php-84:1 /opt /opt/

# Copy your application code
COPY . /var/task

CMD ["Bref\\LaravelBridge\\Queue\\QueueHandler"]
Enter fullscreen mode Exit fullscreen mode

Key differences between the Dockerfiles:

Dockerfile Base Runtime Image CMD Use Case
.web bref/php-84-fpm:2 public/index.php HTTP requests via API Gateway
.cli bref/php-84-console:2 artisan CLI commands, scheduled tasks
.queue bref/php-84:2 QueueHandler SQS queue processing

Serverless Configuration

Here's how to configure serverless.yml to use these Docker images with AWS ECR:

service: my-laravel-app

provider:
  name: aws
  region: us-east-1
  stage: ${opt:stage, 'dev'}
  runtime: provided.al2

  # Optional: Configure API Gateway
  httpApi:
    id: ${ssm:/my-app/apigw_id}  # Or create a new one

  # Environment variables for your Lambda functions
  environment:
    APP_ENV: ${self:provider.stage}
    # Add your other environment variables here

  # VPC configuration (if needed)
  vpc:
    securityGroupIds:
      - sg-xxxxxxxxx
    subnetIds:
      - subnet-xxxxxxxx
      - subnet-yyyyyyyy

  # S3 bucket for Serverless deployments
  deploymentBucket:
    name: my-serverless-deployments

  # ECR image definitions - this is the key part!
  ecr:
    images:
      app-web:
        path: ./
        file: Dockerfile.web
      app-cli:
        path: ./
        file: Dockerfile.cli
      app-queue:
        path: ./
        file: Dockerfile.queue

# Exclude unnecessary files from the Docker build context
package:
  patterns:
    - '!node_modules/**'
    - '!public/storage'
    - '!storage/**'
    - '!tests/**'
    - '!.git/**'
    - '!vendor/**/test/**'
    - '!vendor/**/tests/**'
    - '!vendor/**/docs/**'

functions:
  # Web function - handles HTTP requests
  web:
    name: ${self:provider.stage}-${self:service}-web
    image:
      name: app-web
    timeout: 28
    events:
      - httpApi: '*'

  # Artisan function - for CLI commands and scheduled tasks
  artisan:
    name: ${self:provider.stage}-${self:service}-artisan
    image:
      name: app-cli
    timeout: 120
    events:
      # Run Laravel scheduler every minute
      - schedule:
          rate: rate(1 minute)
          input: '"schedule:run"'

  # Queue worker - processes SQS messages
  queue:
    name: ${self:provider.stage}-${self:service}-queue
    image:
      name: app-queue
    timeout: 60
    events:
      - sqs:
          arn: arn:aws:sqs:us-east-1:123456789:my-queue

plugins:
  - ./vendor/bref/bref
  - ./vendor/bref/extra-php-extensions
Enter fullscreen mode Exit fullscreen mode

Important Configuration Notes

  1. ECR Images Section: The provider.ecr.images section tells Serverless to build Docker images and push them to ECR automatically during deployment.

  2. Function Image Reference: Each function uses image.name to reference the ECR image defined above, instead of using handler and layers.

  3. Package Patterns: Since we're building Docker images, the package patterns help reduce the Docker build context size, making builds faster.

How It All Works Together

When you run serverless deploy:

  1. Serverless builds the Docker images using the specified Dockerfiles
  2. Images are pushed to AWS ECR (Elastic Container Registry) in your account
  3. Lambda functions are created/updated to use these container images
  4. API Gateway, SQS triggers, and schedules are configured

The multi-stage build ensures that:

  • The build dependencies (compilers, dev packages) stay in the build stage
  • Only the compiled extension and runtime dependencies go to the final image
  • The final image is as small and efficient as possible

Testing Locally

You can test your Docker images locally before deploying:

# Build the CLI image
docker build -f Dockerfile.cli -t my-app-cli .

# Test that IMAP is loaded
docker run --rm my-app-cli php -m | grep imap

# Run an artisan command
docker run --rm my-app-cli php artisan list
Enter fullscreen mode Exit fullscreen mode

Conclusion

Using Docker multi-stage builds with Bref is a powerful solution when you need PHP extensions that aren't available as pre-built layers. This approach:

  • Works with any PHP extension - not just IMAP
  • Keeps your images small - build dependencies don't ship to production
  • Is reproducible - the same Dockerfile builds the same image every time
  • Integrates seamlessly with Serverless Framework and AWS ECR

The key is using Bref's bref/build-php-* images for compilation, which ensures compatibility with Lambda's Amazon Linux 2 environment.

If you found this helpful, feel free to reach out with questions or share your own experiences with PHP on Lambda!


Resources


Connect with me: LinkedIn

Top comments (0)