Azure App Service for Linux – Custom Container Shows Apache Default Page and No Files in /home/site/wwwroot
# 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.