Azure App Service for Linux – Custom Container Shows Apache Default Page and No Files in /home/site/wwwroot

Abel Moremi 0 Reputation points
2025-07-17T08:05:21.44+00:00
# Set working directory to the Azure App Service expected folder
WORKDIR /home/site/wwwroot

# Copy GLPI files here
COPY ./ ./

# Set permissions
# Set permissions accordingly
RUN mkdir -p /home/site/wwwroot/files && \
    chown -R www-data:www-data /home/site/wwwroot && \
    chmod -R 755 /home/site/wwwroot

I am deploying GLPI, an open-source PHP-based IT asset management system, to Azure App Service for Linux using a custom Docker container.

The application is containerized using a Dockerfile stored in an Azure DevOps repository. A pipeline builds the Docker image and pushes it to Azure Container Registry (ACR), after which a webhook automatically redeploys the image to Azure App Service.

Environment and Setup:

  • App Service Plan: Linux (Custom Container)
  • Image Base: debian:bookworm-slim
  • Web Server: Apache2 with PHP 8.3
  • CI/CD: Azure DevOps pipeline
  • Registry: Azure Container Registry (ACR)
  • App Code: Forked from GLPI GitHub repository

The Issue:

After image deployment:

The container pulls successfully and starts (as shown in App Service logs).

The application does not load; instead, I see the default Apache2 welcome page.

When connecting to the container (via Kudu/SSH), I found that /home/site/wwwroot is empty.

No errors are visible in the logs, and the Docker image works correctly when run locally with docker run.

This suggests that App Service might be mounting /home/site/wwwroot as a volume, potentially overriding the contents copied during the Docker build.

What I’d Like to Understand:

  • Is this expected behavior in App Service for Linux with custom containers?
  • Should /home/site/wwwroot be avoided when copying application files inside a custom image?
  • What is the recommended pattern when using Apache and custom containers in App Service?

Dockerfile:

FROM debian:bookworm-slim

# Set environment variables for non-interactive install
ENV DEBIAN_FRONTEND=noninteractive

# Install dependencies and PHP 8.3
RUN apt-get update && apt-get install -y --no-install-recommends \
    ca-certificates \
    apt-transport-https \
    lsb-release \
    wget \
    curl \
    gnupg2 && \
    curl -sSLo /usr/share/keyrings/deb.sury.org-php.gpg https://packages.sury.org/php/apt.gpg && \
    echo "deb [signed-by=/usr/share/keyrings/deb.sury.org-php.gpg] https://packages.sury.org/php/ $(lsb_release -sc) main" > /etc/apt/sources.list.d/php.list && \
    apt-get update && apt-get install -y --no-install-recommends \
    apache2 \
    php8.3 \
    php8.3-mysql \
    php8.3-bcmath \
    php8.3-ldap \
    php8.3-xmlrpc \
    php8.3-imap \
    php8.3-curl \
    php8.3-gd \
    php8.3-mbstring \
    php8.3-xml \
    php-cas \
    php8.3-intl \
    php8.3-zip \
    php8.3-bz2 \
    php8.3-redis \
    cron \
    jq \
    libldap-2.5-0 \
    libldap-common \
    libsasl2-2 \
    libsasl2-modules \
    libsasl2-modules-db && \
    rm -rf /var/lib/apt/lists/* && \
    apt-get clean && \
    a2enmod rewrite

WORKDIR /var/www/html

# Copy your GLPI source files
COPY ./ ./ 

# Set permissions
RUN mkdir -p /var/www/html/files && \
    chown -R www-data:www-data /var/www/html && \
    chmod -R 755 /var/www/html
    
# Expose HTTP port
EXPOSE 80

# Start Apache in foreground
CMD ["apachectl", "-D", "FOREGROUND"]

Azure-pipelines.yml

trigger:
  branches:
    include:
      - itiq-dockerize
parameters:
  - name: releaseTag
    displayName: 'Release Tag (e.g., 10.0.18)'
    type: string
    default: '10.0.18'
variables:
  dockerRegistryServiceConnection: '62aec330-84a1-4a58-bd66-e5c95f7e97b8'
  imageRepository: 'glpi'
  containerRegistry: 'itiqcontainers.azurecr.io'
  tag: '$(Build.BuildId)'
  vmImageName: 'ubuntu-latest'
stages:
- stage: Build
  displayName: Build and Push Docker Image
  jobs:
  - job: Build
    displayName: Build GLPI Docker Image
    pool:
      vmImage: $(vmImageName)
    steps:
    - checkout: self
trigger:
  branches:
    include:
      - itiq-dockerize

parameters:
  - name: releaseTag
    displayName: 'Release Tag (e.g., 10.0.18)'
    type: string
    default: '10.0.18'

variables:
  dockerRegistryServiceConnection: '62aec330-84a1-4a58-bd66-e5c95f7e97b8'
  imageRepository: 'glpi'
  containerRegistry: 'itiqcontainers.azurecr.io'
  tag: '$(Build.BuildId)'
  vmImageName: 'ubuntu-latest'

stages:
- stage: Build
  displayName: Build and Push Docker Image
  jobs:
  - job: Build
    displayName: Build GLPI Docker Image
    pool:
      vmImage: $(vmImageName)
    steps:
    - checkout: self
      displayName: Checkout Repo
      clean: true
      persistCredentials: true
      fetchTags: true

    - script: |
        echo "Fetching tags and branch info..."
        git fetch --tags origin itiq-dockerize

        echo "Checking if tag '${{ parameters.releaseTag }}' exists..."
        if git rev-parse "refs/tags/${{ parameters.releaseTag }}" >/dev/null 2>&1; then
          echo "Tag found. Checking out tag ${{ parameters.releaseTag }}."
          git checkout tags/${{ parameters.releaseTag }}
        else
          echo "Tag '${{ parameters.releaseTag }}' not found! Exiting."
          exit 1
        fi

        echo "Overwriting Dockerfile with latest from itiq-dockerize branch..."
        git show origin/itiq-dockerize:.docker/app/Dockerfile > .docker/app/Dockerfile

        echo "Overwriting Apache config (000-default.conf) with latest from itiq-dockerize branch..."
        git show origin/itiq-dockerize:.docker/app/000-default.conf > .docker/app/000-default.conf

        echo "Verifying Dockerfile update:"
        head -n 5 .docker/app/Dockerfile

        echo "Verifying 000-default.conf update:"
        head -n 5 .docker/app/000-default.conf

        echo "Current HEAD commit (tag):"
        git log -1 --oneline
      displayName: 'Checkout Tag and Overwrite Dockerfile + Apache Config'

    - script: |
        echo "Listing root repo files:"
        ls -la $(Build.SourcesDirectory)
      displayName: 'Check repo root contents before Docker build'

    - task: Docker@2
      displayName: Build and Push Docker Image to ACR
      inputs:
        command: buildAndPush
        repository: $(imageRepository)
        dockerfile: $(Build.SourcesDirectory)/.docker/app/Dockerfile
        buildContext: $(Build.SourcesDirectory)
        containerRegistry: $(dockerRegistryServiceConnection)
        tags: |
          ${{ parameters.releaseTag }}
          $(Build.BuildId)
          latest

trigger:
  branches:
    include:
      - itiq-dockerize
parameters:
  - name: releaseTag
    displayName: 'Release Tag (e.g., 10.0.18)'
    type: string
    default: '10.0.18'
variables:
  dockerRegistryServiceConnection: '62aec330-84a1-4a58-bd66-e5c95f7e97b8'
  imageRepository: 'glpi'
  containerRegistry: 'itiqcontainers.azurecr.io'
  tag: '$(Build.BuildId)'
  vmImageName: 'ubuntu-latest'
stages:
- stage: Build
  displayName: Build and Push Docker Image
  jobs:
  - job: Build
    displayName: Build GLPI Docker Image
    pool:
      vmImage: $(vmImageName)
    steps:
    - checkout: self

Summary of the Problem:

Despite successful image builds and webhook deployment to Azure App Service, the custom container does not serve the GLPI application. Instead, the default Apache2 page appears, and /home/site/wwwroot is empty — even though application files were copied there during image build. This behavior does not occur when the image is tested locally using Docker.

Request:

Could you please clarify how Azure App Service for Linux handles /home/site/wwwroot custom containers, and whether this behavior is expected?

Thank you for your assistance.

Azure App Service
Azure App Service
Azure App Service is a service used to create and deploy scalable, mission-critical web apps.
{count} votes

Your answer

Answers can be marked as Accepted Answers by the question author, which helps users to know the answer solved the author's problem.