Articles/AWS/Build a QR Code Lambda and Call It From Laravel

Build a QR Code Lambda and Call It From Laravel

A hands-on, beginner-friendly build: write a tiny Python AWS Lambda that turns text into a QR code, run it locally in Docker with no AWS account, and call it from a Laravel app. Every line of Python is explained for developers coming from PHP.

June 15, 2026·10 min read

You do not need to know Python, and you do not even need an AWS account, to follow this. We are going to build one small, fun thing: a function that turns text into a QR code, run it on your own machine inside Docker, and call it from a Laravel app. I will explain the Python as we go, because if you write PHP, the ideas all rhyme. When you are ready to put it on AWS for real, the next part handles that.

What we are building

A Lambda is just a single function that AWS runs for you on demand. Ours takes some text or a URL and hands back a QR code image. We will package it as a Docker container image (so the image library's native pieces just work), and run it locally with AWS's Runtime Interface Emulator, the same runtime AWS uses in the cloud. That last detail matters: "it works locally" really means "it will work on Lambda."

All you need installed is Docker and a Laravel app (Laravel 13, which wants PHP 8.3+). No AWS account for any of this part.

A little Python first

Here is the entire function. Save it as app.py. Read it once, then I will translate it from PHP-brain to Python-brain.

PYTHON
import base64
import io
import qrcode


def handler(event, context):
    data = event.get("data", "https://franktheprogrammer.com")
    img = qrcode.make(data)
    buffer = io.BytesIO()
    img.save(buffer, format="PNG")
    return {
        "data": data,
        "qr_base64": base64.b64encode(buffer.getvalue()).decode(),
    }

Line by line, in terms you already know:

  • def handler(event, context): is the entry point AWS calls, like a controller method. The body is everything indented under it. There are no braces and no semicolons; indentation is the syntax.
  • event is the input as a dict, Python's associative array. event.get("data", "...") is exactly PHP's $event['data'] ?? '...': read the key, fall back to a default.
  • qrcode.make(data) builds the QR code and returns an image object.
  • We save that image into an in-memory buffer (io.BytesIO() is like php://memory) instead of a file, then base64-encode the raw bytes so they can ride inside JSON.
  • We return a dict, and AWS serializes it to JSON for whoever called us. No $ on variables anywhere.

If any of that syntax feels alien, my Python for PHP Developers guide maps the basics (dicts, f-strings, types) one to one. You do not need it to finish this, but it helps.

The dependencies and the Dockerfile

The function needs the qrcode library, and we want PNG output, so we install the pil extra (that pulls in Pillow, the imaging library). Put this in requirements.txt:

TEXT
qrcode[pil]

Then a Dockerfile next to app.py. AWS publishes base images that already know how to be a Lambda, so it is short:

DOCKERFILE
FROM public.ecr.aws/lambda/python:3.13

COPY requirements.txt ${LAMBDA_TASK_ROOT}/
RUN pip install --no-cache-dir -r requirements.txt

COPY app.py ${LAMBDA_TASK_ROOT}/

CMD ["app.handler"]

${LAMBDA_TASK_ROOT} is where Lambda looks for your code, and CMD ["app.handler"] points at the handler function in app.py. If you want the full story on the base image and choosing an architecture, the reference Package a Python Lambda as a Docker Image goes deep; here we keep moving.

Build the image

One command, from the folder with your three files:

BASH
docker build -t qr-generator .

That produces a container image named qr-generator. On an Apple Silicon Mac this builds for arm64 by default, which is fine.

Run it locally with the Runtime Interface Emulator

Start the container:

BASH
docker run --rm -p 9000:8080 qr-generator

The AWS base image bundles the Runtime Interface Emulator, so the container is now listening exactly like the real Lambda service. In a second terminal, post an event to it. That URL is fixed, and the literal word function is part of it:

BASH
curl -s "http://localhost:9000/2015-03-31/functions/function/invocations" \
  -d '{"data": "https://franktheprogrammer.com"}'

You get back:

JSON
{
  "data": "https://franktheprogrammer.com",
  "qr_base64": "iVBORw0KGgo..."
}

To actually see the QR code, decode that field into a file:

BASH
curl -s "http://localhost:9000/2015-03-31/functions/function/invocations" \
  -d '{"data": "https://franktheprogrammer.com"}' \
  | python3 -c "import sys, json, base64; open('qr.png', 'wb').write(base64.b64decode(json.load(sys.stdin)['qr_base64']))"

Open qr.png and scan it with your phone. That image came out of a function running in Docker on your laptop, with no cloud involved.

Call it from Laravel

Because the container is still listening, Laravel reaches it with a plain HTTP request, the same call our curl made. A controller:

PHP
use Illuminate\Support\Facades\Http;

class QrController extends Controller
{
    public function show()
    {
        $response = Http::post(
            'http://localhost:9000/2015-03-31/functions/function/invocations',
            ['data' => 'https://franktheprogrammer.com'],
        );

        return view('qr', ['qr' => $response->json('qr_base64')]);
    }
}

Http::post($url, $payload) sends the JSON event to the running container, and $response->json('qr_base64') plucks that one field out of the reply. Hand it to a Blade view, where a data URI renders the PNG inline with no file on disk:

HTML
<img src="data:image/png;base64,{{ $qr }}" alt="QR code">

Load the page and the QR code appears, generated by your Python Lambda and rendered by your PHP app. The only thing to remember is that the container has to be running (docker run) for Laravel to reach it. This is your local development loop; in production you would call the deployed function instead, which is the next part.

Make it yours

Now that the loop is tight, edit app.py, rebuild, and re-run:

BASH
docker build -t qr-generator . && docker run --rm -p 9000:8080 qr-generator

For example, swap the one-line qrcode.make() for the fuller API to size it up and brand the color:

PYTHON
qr = qrcode.QRCode(box_size=10, border=2)
qr.add_data(data)
img = qr.make_image(fill_color="#2f6bff", back_color="white")

Edit, rebuild, hit the endpoint, refresh Laravel. That is the whole inner loop, and none of it has touched AWS yet.

You have built and run a real Lambda entirely on your machine and called it from Laravel. When you want to put it online so the deployed app (or anyone) can reach it, the next part creates the ECR repository, pushes this exact image, and sets up the function in the AWS Console.