Deploy a PHP Application with CI
TMXIO deploys generic PHP applications as immutable release artifacts. Build the application and its production dependencies in CI, upload one tar.gz file, and ask TMXIO to activate that exact artifact. This guide targets the production Website Environment and its production-scoped deploy token.
TMXIO does not run Composer, frontend builds, or framework deployment scripts while unpacking the artifact. The uploaded release must already contain everything the application needs to serve traffic.
Configure the Website Environment
Section titled “Configure the Website Environment”Open the production Website Environment and review PHP runtime:
- Select the PHP version your application supports.
- Set Public root to the directory containing the public entry point. Laravel and Symfony commonly use
public. - Choose Front controller for frameworks that route requests through
index.php. Choose Direct files only when requests map directly to PHP files. - Set Health path to a fast public route that returns HTTP 200 without depending on optional services.
- Add an optional Release command only when activation requires a bounded command such as
php artisan migrate --force. - Under PHP settings, leave the memory limit, maximum input variables, OPcache memory, and OPcache interned strings fields empty to inherit platform defaults. Set an override only when this environment needs a different value. Saving an effective change reconciles this environment’s PHP-FPM pool and may briefly interrupt in-flight PHP requests.
- Save application configuration under Environment variables. Store credentials and application keys as secrets.
- Under Custom domains, add the fully qualified domain name (FQDN) that should serve the application and follow the displayed DNS instructions.
- Copy the numeric Environment ID from Environment Information. CI uses it to pin uploads and deploys to this exact Website Environment.
Release commands run after extraction and before activation. If the command fails, TMXIO rejects the candidate release and keeps the previous release active. Database changes must remain compatible with that rollback boundary.
Generate the deploy token
Section titled “Generate the deploy token”Under Artifact deploy token, select Generate token. Save the token immediately: TMXIO displays it only once.
In GitLab, add a protected and masked CI/CD variable named:
TMXIO_ARTIFACT_DEPLOY_TOKENAdd the numeric value copied from Environment Information as a second CI/CD variable:
TMXIO_ENVIRONMENT_IDThe token is scoped to the Website Environment. Rotate it when it is exposed or when CI ownership changes. Rotation immediately invalidates the previous value.
Package the release
Section titled “Package the release”The release archive should contain the application at its root, including:
- the public entry point under the configured Public root;
- production Composer dependencies in
vendor/; - built frontend assets;
- writable-directory placeholders required by the framework.
Do not include .env files, credentials, Git history, development dependencies, test output, or local caches. Configure runtime values in TMXIO instead.
GitLab CI example
Section titled “GitLab CI example”Set TMXIO_SITE_ID to the Website slug shown in TMXIO and TMXIO_ENVIRONMENT_ID to the numeric ID shown on the production Website Environment screen. This example builds on PHP 8.4, creates release.tar.gz, uploads it through the TMXIO artifact API, and activates it only on that Website Environment.
stages: - build - deploy
default: tags: - docker
variables: TMXIO_API_ORIGIN: "https://app.tmxio.com" TMXIO_SITE_ID: "your-website-slug"
build: stage: build image: php:8.4-cli-alpine before_script: - apk add --no-cache composer nodejs npm script: - composer install --no-dev --classmap-authoritative --no-interaction --no-progress - npm ci - npm run build - rm -rf node_modules - tar --exclude='./.git' --exclude='./.env' --exclude='./.env.*' --exclude='./release.tar.gz' --exclude='./tests' -czf release.tar.gz . artifacts: paths: - release.tar.gz expire_in: 1 day
deploy: stage: deploy image: alpine:3.22 needs: - job: build artifacts: true before_script: - apk add --no-cache curl jq script: - | set -eu API="${TMXIO_API_ORIGIN%/}/api/v1/sites/${TMXIO_SITE_ID}" ENVIRONMENT_QUERY="?environment_id=${TMXIO_ENVIRONMENT_ID:?set this from the TMXIO Environment Information card}" SHA256="$(sha256sum release.tar.gz | awk '{print $1}')" SIZE="$(wc -c < release.tar.gz | tr -d ' ')"
RESPONSE="$(curl --fail-with-body --silent --show-error \ -X POST "${API}/releases/upload-url${ENVIRONMENT_QUERY}" \ -H "Authorization: Bearer ${TMXIO_ARTIFACT_DEPLOY_TOKEN}" \ -H 'Content-Type: application/json' \ --data "{\"sha256\":\"${SHA256}\",\"size\":${SIZE}}")"
OBJECT_KEY="$(printf '%s' "$RESPONSE" | jq -er '.object_key')" if [ "$(printf '%s' "$RESPONSE" | jq -er '.already_present')" != "true" ]; then UPLOAD_URL="$(printf '%s' "$RESPONSE" | jq -er '.upload_url')" curl --fail-with-body --silent --show-error \ -X PUT --upload-file release.tar.gz "$UPLOAD_URL" fi
DEPLOY_RESPONSE="$(curl --fail-with-body --silent --show-error \ -X POST "${API}/deploys${ENVIRONMENT_QUERY}" \ -H "Authorization: Bearer ${TMXIO_ARTIFACT_DEPLOY_TOKEN}" \ -H 'Content-Type: application/json' \ --data "{\"source_kind\":\"artifact\",\"object_key\":\"${OBJECT_KEY}\",\"sha256\":\"${SHA256}\",\"label\":\"${CI_COMMIT_SHA}\"}")"
DEPLOY_ID="$(printf '%s' "$DEPLOY_RESPONSE" | jq -er '.deploy.id')" for ATTEMPT in $(seq 1 60); do STATE_RESPONSE="$(curl --fail-with-body --silent --show-error \ "${API}/deploys/${DEPLOY_ID}${ENVIRONMENT_QUERY}" \ -H "Authorization: Bearer ${TMXIO_ARTIFACT_DEPLOY_TOKEN}")" STATE="$(printf '%s' "$STATE_RESPONSE" | jq -er '.deploy.state')"
case "$STATE" in healthy) echo "Deployment ${DEPLOY_ID} is healthy." exit 0 ;; failed) printf '%s' "$STATE_RESPONSE" | jq '.deploy | {state, failed_phase, error_tail, output_tail}' exit 1 ;; esac
echo "Deployment ${DEPLOY_ID} is ${STATE} (attempt ${ATTEMPT}/60)." sleep 5 done
echo "Deployment ${DEPLOY_ID} did not finish within 5 minutes." >&2 exit 1 environment: name: production rules: - if: '$CI_COMMIT_BRANCH == $CI_DEFAULT_BRANCH'The example selects a GitLab runner tagged docker. If your GitLab instance exposes its container runner under the shared tag instead, replace docker with shared.
If the repository has no frontend build, remove the Node.js installation and npm commands. If it uses another build tool, replace those commands while preserving the self-contained artifact boundary.
Verify the deployment
Section titled “Verify the deployment”Open Deploys and follow the new release until it reaches Healthy or Failed. A queued or running deploy is not complete.
After TMXIO reports Healthy:
- Open the public Website domain.
- Request the configured health path.
- Check application routes that depend on environment variables or databases.
- Review the release-command output when a command was configured.
If activation fails, open the failed deploy to read the failed phase and error tail. Correct the build or configuration and upload a new artifact. Use rollback only to reactivate an artifact that was previously deployed successfully.