Creating an HTTP Function using a container image built with PHP¶
For general details about how to use a container image to create and execute an HTTP function, see Creating an HTTP Function Using a Container Image and executing the Function.
This chapter introduces how to create an image using PHP and perform local verification for HTTP functions.
Note
You need to implement an HTTP server in the image listening to port 8000 to receive requests.
Step 1: Create the Project¶
In this example we use the FlightPHP framework to create an HTTP server.
For full example, see: container-http-flightphp sample in the GitHub repository.
Initialize the project:¶
First, create a project directory:
mkdir -p my-http-function/src
cd my-http-function
Then, create a composer.json file in the project root folder and add the following content:
{
"name": "opentelekomcloud-community/otc-functiongraph-php-runtime-samples-doc-container-http-flightphp",
"description": "Sample code for FunctionGraph container http function with FlightPHP framework",
"type": "project",
"license": "Apache-2.0",
"require": {
"php": "^8.2",
"flightphp/core": "^3.18",
"monolog/monolog": "^3.10",
"ext-json": "*"
},
"archive": {
"exclude": [
"Makefile",
"terraform",
"resources"
]
},
"config": {
"archive-format": "zip",
"process-timeout": 0,
"sort-packages": true
}
}
Install the dependencies using composer:
composer install
Implementing the function¶
Next, create following file:
src/index.php for the function entry
<?php
// if installed with composer
require '../vendor/autoload.php';
require __DIR__ . '/loggingmiddleware.php';
Flight::group('', function () {
// Deliver favicon.ico with 204 No Content to avoid unnecessary 404 errors in logs.
Flight::route('/favicon.ico', function () {
Flight::response()->status(204);
Flight::response()->send();
});
// example with query parameter
Flight::route('/search', function () {
$name = Flight::request()->query->name ?? '';
if ($name == '') {
echo 'you searched for nothing, specify a name with ?name=yourname';
} else {
echo 'you searched for: ' . $name;
}
});
// example with optional path parameter
Flight::route('/user(/@name)', function (?string $name) {
if ($name == '') {
echo 'hello unknown';
} else {
echo 'hello ' . $name;
}
})->setAlias('user');
// Example route that returns JSON data
Flight::route('/json', function () {
$logger = Flight::get('logger');
$logger->info('Handling /json request');
$ak = Flight::get('cffAccessKey');
$body = Flight::request()->getBody();
$logger->debug($body);
$dec_body = json_decode($body, true);
$name = $dec_body['name'] ?? 'unknown';
$logger->debug('DEBUG');
$logger->info('INFO');
$logger->error('ERROR');
Flight::json([
'hello' => $name,
'AK' => $ak,
'USER_DATA_ENVVAR_1' => (string) getenv("USER_DATA_ENVVAR_1"),
'SECRET_ENVVAR_1' => (string) getenv("SECRET_ENVVAR_1")
]);
})->setAlias('json');
}, [LoggingMiddleware::class]);
Flight::start();
In this code, we create a FlightPHP application that listens on port 8000.
Step 2: Build the Container Image¶
Create a Makefile¶
To simplify the development and testing process, create a Makefile in the project root folder:
SHELL:=/bin/bash
TARGET_PATH=target
DOCKER_FILE=Dockerfile
IMAGE_NAME=custom_container_http_flightphp_php
docker_build:
docker buildx build \
--platform linux/amd64 \
--build-arg DOCKER_BUILD_TIMESTAMP="$$(date -u +%Y-%m-%dT%H:%M:%SZ)" \
--file $(DOCKER_FILE) \
--tag $(IMAGE_NAME):latest .
docker_push: docker_build
# see: https://docs.otc.t-systems.com/software-repository-container/umn/image_management/obtaining_a_long-term_valid_login_command.html#swr-01-1000
@echo $(OTC_SWR_LOGIN_KEY) | docker login -u $(OTC_SDK_PROJECTNAME)@$(OTC_SDK_AK) --password-stdin $(OTC_SWR_ENDPOINT)
docker tag $(IMAGE_NAME):latest $(OTC_SWR_ENDPOINT)/$(OTC_SWR_ORGANIZATION)/$(IMAGE_NAME):latest
docker push $(OTC_SWR_ENDPOINT)/$(OTC_SWR_ORGANIZATION)/$(IMAGE_NAME):latest
docker_all: docker_build docker_push
############################################################################
docker_run_local:
-docker rm -f $(IMAGE_NAME)
docker container run \
-d \
--rm \
--platform linux/amd64 \
--publish 8000:8000 \
--user 1003:1003 \
--name $(IMAGE_NAME) \
$(IMAGE_NAME):latest
docker_stop_local:
-docker rm -f $(IMAGE_NAME)
run_local:
php -S 0.0.0.0:8000 -t src
#############################################################################################
# Test targets
#############################################################################################
test_local_get_search:
# execute a curl request against the local function
curl -X GET \
-H 'Content-Type: application/json' \
http://localhost:8000/search?name=John
@echo ""
test_local_post_json:
# execute a curl request against the local function
curl -X POST \
-H 'Content-Type: application/json' \
-d @./resources/curl_apig_post_index.json \
http://localhost:8000/json
@echo ""
.PHONY: docker_build docker_run_local docker_stop_local docker_push docker_all test_local test_local_get_search test_local_post_json
Create a Dockerfile¶
Create a Dockerfile in the project root folder to define the image.
Note
- In the cloud environment, UID 1003 and GID 1003 are used to start the container by default.The two IDs can be modified by choosing Configuration > Basic Settings > Container Image Overrideon the function details page. They cannot be root or a reserved ID.
- If the base image of the Alpine version is used, run the addgroup and adduser instead of groupadd and useradd commands.
You can use any base image that meets your application requirements.
Note
Ubuntu images are larger in size but come with more pre-installed libraries.
Alpine images are smaller in size but may require additional libraries depending on the application requirements.
Following example uses a PHP 8.2.33 base image.
# see: https://hub.docker.com/_/composer
FROM composer:2@sha256:4d71c3c2109c61d5415544264b59ad4087e4c5b7244481723664138fd36d5040 AS composer
# see: https://hub.docker.com/_/php/tags?name=alpine
FROM php:8.2.33-zts-alpine3.23@sha256:58b967d06f56a75d652162f48205bb03d2302f18322bcdbc24ffe17c749cd322
ARG DOCKER_BUILD_TIMESTAMP=unknown
ENV DOCKER_BUILD_TIMESTAMP=${DOCKER_BUILD_TIMESTAMP}
ENV HOME=/home/paas_user \
GROUP_ID=1003 \
GROUP_NAME=paas_user \
USER_ID=1003 \
USER_NAME=paas_user \
TZ=Etc/UTC
COPY --from=composer /usr/bin/composer /usr/local/bin/composer
RUN apk add --no-cache tzdata && \
mkdir -p ${HOME} && \
# add group with specific GID
addgroup -g ${GROUP_ID} ${GROUP_NAME} && \
# add user with specific UID and GID
adduser -u ${USER_ID} -G ${GROUP_NAME} -D ${USER_NAME}
WORKDIR ${HOME}
# Copy composer.json and source code
COPY composer.json ${HOME}
COPY src ${HOME}/src
# Install dependencies
RUN cd ${HOME} && \
composer install --no-dev --no-interaction --optimize-autoloader
# Copy entrypoint script
COPY ./entrypoint.sh ${HOME}/entrypoint.sh
# adjust permissions
RUN chown -R ${USER_ID}:${GROUP_ID} ${HOME} && \
chmod -R 550 ${HOME}
EXPOSE 8000
# switch to non root user
USER ${USER_NAME}
ENTRYPOINT ["sh", "/home/paas_user/entrypoint.sh"]
Create following entrypoint script to start the server in the container:
#!/bin/sh
cd /home/paas_user
php -S 0.0.0.0:8000 -t src
Build and verify the image locally¶
1. Build the image
Build the image either using docker build or the Makefile target docker_build:
Run the following command in the project root folder to build the image:
docker buildx build \
--platform linux/amd64 \
--file Dockerfile \
--tag custom_container_http_flightphp:latest .
Run the following command in the project root folder to build the image:
make docker_build
2. Run the image locally
Run the image either using docker run or the Makefile target docker_run_local:
Run the following command in the project root folder to run the image:
docker container run --rm \
--platform linux/amd64 \
--publish 8000:8000 \
--name custom_container_http_flightphp \
custom_container_http_flightphp:latest
Run the following command in the project root folder to run the image:
make docker_run_local
3. Test the image locally
Test the image either using curl or the Makefile target test_local:
Run the following command in a new terminal to test the image using a curl command:
curl -X GET localhost:8000/search?name=John
Run the following command in a new terminal to test the image:
make test_local_get_search
You should see output similar to the following:
you searched for: John
Step 3: Upload the Container Image to SWR (SoftWare Repository for Container)¶
For details on SWR (SoftWare Repository for Container), see:
Prerequisites¶
SWR instance created.
Credentials for SWR created.
Upload the image to SWR¶
To upload the container image to SWR, following values are needed:
Parameter |
Description |
|---|---|
OTC_SDK_PROJECTNAME |
Your project name.
To obtain this, see: Obtaining a Project ID
in API usage guide but use the project name instead of the project ID.
|
OTC_SDK_AK |
Your Access Key |
OTC_SWR_LOGIN_KEY |
The login key for SWR.
For details see: Obtaining a Long-Term Docker Login Command
in the Software Repository for Container user manual.
It can be generated using the access key ${OTC_SDK_AK} and secret key ${OTC_SDK_SK} as follows:
|
OTC_SWR_ENDPOINT |
SWR endpoint, e.g. swr.eu-de.otc.t-systems.com |
OTC_SWR_ORGANIZATION |
Your SWR organization name |
IMAGE_NAME |
The name of your container image |
- Set the environment variables:
export OTC_SDK_PROJECTNAME=<your_project_name> export OTC_SDK_AK=<your_access_key> export OTC_SDK_SK=<your_secret_key> export OTC_SWR_LOGIN_KEY=$(printf "${OTC_SDK_AK}" | \ openssl dgst -binary -sha256 -hmac "${OTC_SDK_SK}" | \ od -An -vtx1 | sed 's/[ \n]//g' | sed 'N;s/\n//') export OTC_SWR_ENDPOINT=swr.eu-de.otc.t-systems.com export OTC_SWR_ORGANIZATION=<your_swr_organization> export IMAGE_NAME=custom_container_event_example
Upload the image to SWR either using shell commands or the Makefile target docker_push:
Run the following commands in the container-event folder to upload the image to SWR:
docker login -u ${OTC_SDK_PROJECTNAME}@${OTC_SDK_AK} -p ${OTC_SWR_LOGIN_KEY} ${OTC_SWR_ENDPOINT}
docker tag ${IMAGE_NAME}:latest ${OTC_SWR_ENDPOINT}/${OTC_SWR_ORGANIZATION}/${IMAGE_NAME}:latest
docker push ${OTC_SWR_ENDPOINT}/${OTC_SWR_ORGANIZATION}/${IMAGE_NAME}:latest
Run the following command in the container-event folder to upload the image to SWR:
make docker_push
Step 4: Create an HTTP Function Using the Container Image¶
In the left navigation pane of the management console, choose Compute > FunctionGraph. On the FunctionGraph console, choose Functions > Function List from the navigation pane.
Click Create Function in the upper right corner. On the displayed page, select Container Image for creation mode.
Set the basic function information.
Function Type: Select HTTP Function.
Region: The default value is used. You can select other regions.
Regions are geographic areas isolated from each other. Resources are region-specific and cannot be used across regions through internal network connections. For low network latency and quick resource access, select the nearest region.
Function Name: Enter e.g. custom_container_http.
Enterprise Project: The default value is default. You can select the created enterprise project.
Enterprise projects let you manage cloud resources and users by project.
Agency: Select an agency with the SWR Admin permission. If no agency is available, create one by referring to Creating an Agency.
Container Image: Enter the image uploaded to SWR. The format is: {SWR_endpoint}/{organization_name}/{image_name}:{tag}.
Example: swr.eu-de.otc.t-systems.com/my_organization/custom_container_http_example:latest.
Advanced Settings: Collect Logs is disabled by default. If it is enabled, function execution logs will be reported to Log Tank Service (LTS). You will be billed for log management on a pay-per-use basis.
Parameter
Description
Log Configuration
You can select Auto or Custom.
Auto: Use the default log group and log stream. Log groups prefixed with “functiongraph.log.group” are filtered out.
Custom: Select a custom log group and log stream. Log streams that are in the same enterprise project as your function.
Log Tag
You can use these tags to filter function logs in LTS.You can add 10 more tags.Tag key/value: Enter a maximum of 64 characters.Only digits, letters, underscores (_), and hyphens (-) are allowed.After the configuration is complete, click Create Function.
See also: Step 4: Creating Function in the user manual.
Step 5: Test the HTTP Function¶
On the function details page, click Test. In the displayed dialog box, create following test events.
See also: Step 5: Testing the Function in the user manual.
Test event for GET /search¶
set Event Name to get_search,
modify the test event as follows,
Test event for GET /search¶{ "body": "", "requestContext": { "apiId": "bc1dcffd-aa35-474d-897c-d53425a4c08e", "requestId": "11cdcdcf33949dc6d722640a13091c77", "stage": "RELEASE" }, "queryStringParameters": { "responseType": "application/json", "name": "John" }, "httpMethod": "GET", "pathParameters": { }, "headers": { "accept-language": "q=0.5,en-US;q=0.3,en;q=0.2", "accept-encoding": "gzip, deflate, br", "x-forwarded-port": "443", "x-forwarded-for": "103.218.216.98", "accept": "application/json", "upgrade-insecure-requests": "1", "host": "host", "x-forwarded-proto": "https", "pragma": "no-cache", "cache-control": "no-cache", "x-real-ip": "103.218.216.98", "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0" }, "path": "/search", "isBase64Encoded": false }
and click Create.
Test event for POST /json¶
set Event Name to post_json,
modify the test event as follows,
Test event for POST /json¶{ "body": "{ \"name\": \"T Cloud Public\" }", "requestContext": { "apiId": "bc1dcffd-aa35-474d-897c-d53425a4c08e", "requestId": "11cdcdcf33949dc6d722640a13091c77", "stage": "RELEASE" }, "queryStringParameters": { "responseType": "application/json" }, "httpMethod": "POST", "pathParameters": { }, "headers": { "accept-language": "q=0.5,en-US;q=0.3,en;q=0.2", "accept-encoding": "gzip, deflate, br", "x-forwarded-port": "443", "x-forwarded-for": "103.218.216.98", "accept": "application/json", "upgrade-insecure-requests": "1", "host": "host", "x-forwarded-proto": "https", "pragma": "no-cache", "cache-control": "no-cache", "x-real-ip": "103.218.216.98", "user-agent": "Mozilla/5.0 (Windows NT 6.1; Win64; x64; rv:57.0) Gecko/20100101 Firefox/57.0" }, "path": "/json", "isBase64Encoded": false }
and click Create.
Step 6: View the Execution Result¶
Click Test to execute the function and you should see the following output in the Execution Result section on the right for get_search test event:
{
"body": "eW91IHNlYXJjaGVkIGZvcjogSm9obg==",
"headers": {
"Content-Length": [
"22"
],
"Content-Type": [
"text/html; charset=UTF-8"
],
"Date": [
"Tue, 08 Sep 2026 06:25:02 GMT"
],
"Host": [
"host"
],
"X-Cff-Request-Id": [
"4adc025c-e728-4db0-999c-ca369308d6fd"
],
"X-Powered-By": [
"PHP/8.2.33"
]
},
"statusCode": 200,
"isBase64Encoded": true
}
The response body is Base64-encoded. After decoding, you will get the string “you searched for: John”.
For post_json test event the output should be:
{
"body": "eyJoZWxsbyI6IlQgQ2xvdWQgUHVibGljIiwiQUsiOiJubyBhZ2VuY3kvaW5jbHVkZSBrZXlzIn0=",
"headers": {
"Content-Length": [
"56"
],
"Content-Type": [
"application/json"
],
"Date": [
"Tue, 08 Sep 2026 06:40:44 GMT"
],
"Host": [
"host"
],
"X-Cff-Request-Id": [
"c34ebd61-d6ff-4be6-a920-d2010d6b9d7b"
],
"X-Powered-By": [
"PHP/8.2.33"
]
},
"statusCode": 200,
"isBase64Encoded": true
}
After decoding, you will get the string:
{"hello":"T Cloud Public","AK":"no agency/include keys"}