Kokil Thapa - Professional Web Developer in Nepal
Freelancer Web Developer in Nepal with 15+ Years of Experience

Kokil Thapa is an experienced full-stack web developer focused on building fast, secure, and scalable web applications. He helps businesses and individuals create SEO-friendly, user-focused digital platforms designed for long-term growth.

Azure App Service: Deploy a Web App the Easy Way

By Kokil Thapa | Last reviewed: August 2026

Azure App Service: Deploy a Web App the Easy Way is a common search because teams want managed hosting without the operational overhead of virtual machines. For developers accustomed to Linux VPS environments or platform-as-a-service tools like Heroku, Azure offers a middle ground where infrastructure is abstracted but configuration remains granular. This guide covers the exact workflow I use to ship PHP and Laravel applications to Azure, focusing on CLI-driven reproducibility rather than portal clicks.

While many tutorials focus on .NET, the reality for Laravel developers and PHP practitioners is that Azure’s Linux App Service is now a first-class citizen. The "easy way" isn't clicking through wizards; it's establishing a repeatable command-line workflow that treats infrastructure as code. Whether you are migrating a legacy WordPress site or deploying a fresh SaaS API, the principles of immutable deployments and environment isolation remain constant.

How do you provision Azure App Service via CLI for PHP?

The Azure Portal is useful for exploration, but for production workloads, the CLI prevents configuration drift. When setting up Azure App Service: Deploy a Web App the Easy Way, you need three resources grouped together: a Resource Group, an App Service Plan, and the Web App itself. For PHP and Laravel, always choose Linux. Windows App Service plans run PHP via FastCGI in IIS, which introduces latency and filesystem permission quirks that don't exist on native Linux containers.

Create the resource group and plan

Start by creating a dedicated resource group. This keeps billing and access control isolated. Then provision the App Service Plan. In 2026, the B1 (Basic) tier is the practical minimum for any business application. The F1 (Free) and D1 (Shared) tiers lack custom domain support, SSL binding, and scale-out capabilities. They also share CPU cycles with other tenants, making them unsuitable for anything beyond hobby projects.

# Create resource group
az group create --name rg-legal-portal-prod --location eastus

# Create Linux App Service Plan (B1 Basic)
az appservice plan create \
  --name asp-legal-portal \
  --resource-group rg-legal-portal-prod \
  --sku B1 \
  --is-linux

# Create Web App with PHP 8.4 runtime
az webapp create \
  --name legal-portal-app \
  --resource-group rg-legal-portal-prod \
  --plan asp-legal-portal \
  --runtime "PHP|8.4"

Note the explicit runtime specification. Azure defaults can lag behind current stable releases. As of mid-2026, PHP 8.4 is the latest stable version supported on Azure App Service Linux. If you omit the runtime flag, you may inherit an older default. Always verify available runtimes before provisioning:

az webapp list-runtimes --linux --query "[?starts_with(runtime, 'PHP')]"
Resource Group: rg-legal-portal-prodApp Service Plan (Linux)SKU: B1 BasicOS: Ubuntu 22.04 LTSRegion: East USWeb App InstanceRuntime: PHP 8.4Name: legal-portal-appURL: *.azurewebsites.netCritical Configuration Notes• Always use Linux plan for PHP/Laravel (avoid Windows/IIS)• B1+ required for custom domains, SSL, and staging slots• Specify runtime explicitly (--runtime "PHP|8.4")• Shared/Free tiers lack production-grade isolation
Azure App Service resource hierarchy for PHP deployments — Resource Group contains both the Plan and Web App

Configure application settings securely

Never commit secrets to source control. Azure App Settings are injected as environment variables at runtime and encrypted at rest. For Laravel, map your .env values directly:

az webapp config appsettings set \
  --name legal-portal-app \
  --resource-group rg-legal-portal-prod \
  --settings \
    APP_ENV=production \
    APP_DEBUG=false \
    DB_HOST=my-sql-server.mysql.database.azure.com \
    DB_DATABASE=legal_portal_prod \
    DB_USERNAME=app_user \
    DB_PASSWORD=@KeyVaultReference(...) \
    CACHE_DRIVER=redis \
    QUEUE_CONNECTION=redis

For database passwords and API keys, integrate Azure Key Vault references instead of storing plaintext values. This adds a layer of security auditability that matters for legal-tech and financial applications where compliance is non-negotiable.

What is the best deployment method for Azure App Service?

There are four primary ways to execute Azure App Service: Deploy a Web App the Easy Way, but only two are viable for production. FTP is insecure and lacks atomicity. Local Git is acceptable for solo prototypes. For real applications, choose between GitHub Actions (recommended for most teams) and Azure DevOps Pipelines (common in enterprise Microsoft shops).

MethodBest ForZero-DowntimeBuild LocationComplexity
GitHub ActionsOpen-source & SMB teamsYes (with slots)GitHub runnersLow
Azure DevOpsEnterprise / Microsoft ecosystemYes (with slots)Microsoft-hosted agentsMedium
Local GitPrototypes / solo devsNoKudu on App ServiceVery Low
FTP/SFTPEmergency hotfixes onlyNoNone (pre-built)Low (but risky)

GitHub Actions workflow for Laravel

The key insight for Azure App Service: Deploy a Web App the Easy Way with CI/CD is separating build from deploy. Build artifacts on GitHub's infrastructure, then push only the compiled output to Azure. This avoids running composer install or npm run build on the production server, which consumes CPU/memory during deployment windows.

# .github/workflows/azure-deploy.yml
name: Deploy to Azure App Service

on:
  push:
    branches: [main]

jobs:
  build-and-deploy:
    runs-on: ubuntu-latest
    steps:
      - uses: actions/checkout@v4

      - name: Setup PHP 8.4
        uses: shivammathur/setup-php@v2
        with:
          php-version: '8.4'
          extensions: mbstring, pdo_mysql, redis, zip

      - name: Install Composer dependencies
        run: composer install --no-dev --optimize-autoloader

      - name: Build frontend assets
        run: |
          npm ci
          npm run build

      - name: Deploy to Azure Web App
        uses: azure/webapps-deploy@v3
        with:
          app-name: legal-portal-app
          slot-name: staging
          package: .
          publish-profile: ${{ secrets.AZURE_PUBLISH_PROFILE }}

This workflow targets the staging slot, not production directly. That distinction is what separates professional deployments from amateur ones. The publish profile should be stored as a GitHub secret, never hardcoded.

Why Kudu builds fail for complex apps

If you use Local Git or ZIP deploy without pre-building, Azure's Kudu engine attempts to detect your framework and run build commands automatically. For simple static sites this works. For Laravel applications with private Composer repositories, custom Node toolchains, or specific PHP extensions, Kudu builds frequently fail due to missing SSH keys, memory limits, or incompatible system libraries. Pre-building in CI eliminates this entire category of failures.

GitHub RepoPush to mainTriggers workflowGitHub Actionscomposer install --no-devnpm ci && npm run buildPackage artifactsStaging SlotDeploy packageRun migrationsSmoke testsProduction SlotSwap after validationZero downtimeInstant rollbacktriggerdeployswapWhy This Flow Matters• Build happens off-server → no CPU spike during deploy• Staging slot validates before touching production traffic• Swap is atomic DNS/routing change → users see no errors• Rollback = reverse swap (seconds, not re-deploy minutes)
Recommended CI/CD pipeline for Azure App Service: Deploy a Web App the Easy Way with staging slots

How do deployment slots enable zero-downtime releases?

Deployment slots are the single most important feature for anyone treating Azure App Service: Deploy a Web App the Easy Way as a production platform. A slot is a fully independent App Service instance with its own URL, app settings, and connection strings. You deploy to the slot, validate it, then swap it into production. The swap operation exchanges virtual IP addresses, meaning existing connections drain gracefully and new requests route instantly.

Create and configure a staging slot

# Create staging slot
az webapp deployment slot create \
  --name legal-portal-app \
  --resource-group rg-legal-portal-prod \
  --slot staging

# Configure slot-specific settings (not swapped)
az webapp config appsettings set \
  --name legal-portal-app \
  --resource-group rg-legal-portal-prod \
  --slot staging \
  --slot-settings \
    APP_URL=https://legal-portal-app-staging.azurewebsites.net \
    MAIL_MAILER=log

The --slot-settings flag marks settings as sticky to the slot. These values stay with the slot during swaps, preventing staging URLs or debug mailers from leaking into production. Production-only settings like payment gateway credentials should similarly be marked sticky on the production slot.

Execute the swap safely

# Validate staging health before swap
curl -sf https://legal-portal-app-staging.azurewebsites.net/health || exit 1

# Perform swap
az webapp deployment slot swap \
  --name legal-portal-app \
  --resource-group rg-legal-portal-prod \
  --slot staging \
  --target-slot production

# Verify production health post-swap
curl -sf https://legal-portal-app.azurewebsites.net/health || \
  az webapp deployment slot swap \
    --name legal-portal-app \
    --resource-group rg-legal-portal-prod \
    --slot production \
    --target-slot staging

Always automate health checks before and after swaps. If the post-swap check fails, immediately swap back. This gives you sub-minute rollback capability compared to re-running a full deployment pipeline. For legal-tech portals handling sensitive document uploads or court date tracking, this reliability guarantee is non-negotiable.

What Laravel-specific configurations does Azure App Service require?

Azure App Service Linux runs PHP in a containerized environment with some constraints that differ from traditional VPS setups. Understanding these prevents the "works locally, breaks on Azure" problems I've encountered repeatedly on client projects.

Filesystem persistence and storage

Azure App Service provides persistent storage at /home. All other paths are ephemeral and reset on restarts. Laravel's storage directory must be symlinked or configured to use this persistent path:

# In your startup script or custom Dockerfile
ln -s /home/site/wwwroot/storage /var/www/html/storage

# Or set via App Setting
STORAGE_PATH=/home/site/wwwroot/storage

Better yet, offload file storage to Azure Blob Storage entirely. Use the league/flysystem-azure-blob-storage adapter so your application doesn't depend on local disk persistence. This also enables CDN integration for media-heavy sites like florist eCommerce platforms or travel agency galleries.

OPcache and performance tuning

Azure enables OPcache by default, but the default settings are conservative for development safety. For production Laravel applications, override these via a custom .ini file placed in /home/site/config/php.ini:

[opcache]
opcache.enable=1
opcache.memory_consumption=256
opcache.max_accelerated_files=20000
opcache.validate_timestamps=0
opcache.revalidate_freq=0

[php]
upload_max_filesize=64M
post_max_size=64M
max_execution_time=300

Setting validate_timestamps=0 means OPcache never checks for file changes. This is safe because deployment slots provide atomic updates — new code arrives as a complete unit, not incremental file modifications. Remember to restart the App Service after changing INI settings, or include them in your deployment artifact.

Queue workers and scheduled tasks

Azure App Service doesn't natively support long-running processes like Laravel queue workers. You have two options. For low-volume queues, use Azure Functions triggered by Redis/Service Bus. For consistent workload, add an Azure Container App or VM running php artisan queue:work as a systemd service. Never rely on HTTP-triggered cron endpoints for critical background processing; they're subject to request timeouts and scaling limitations.

For scheduled tasks, use Azure Logic Apps or GitHub Actions scheduled workflows to hit your scheduler endpoint, or better yet, use the Azure Scheduler replacement (Logic Apps) to invoke php artisan schedule:run via SSH or a secure webhook every minute. Documenting this architecture clearly helps when handing off maintenance to clients who may later seek guidance on website development costs and ongoing operational expenses.

Azure App ServicePHP 8.4 + LaravelOPcache enabled/home (persistent)Ephemeral /tmp⚠ No long-running processes⚠ No cron daemonAzure Blob StorageUser uploadsMedia assetsDocument storageCDN-integrated✓ Flysystem adapter✓ Decoupled from computeExternal WorkersContainer App / VMqueue:work daemonLogic Apps schedulerRedis / Service Bus✓ True background processing✓ Independent scalingCommon Pitfalls to Avoid• Writing logs/uploads to /var/www/html (lost on restart)• Running queue workers inside App Service (killed after timeout)• Using HTTP cron triggers for critical jobs (unreliable)• Skipping OPcache tuning (leaves 3-5x performance on table)• Not marking slot-specific settings as sticky
Laravel on Azure App Service: persistent storage, blob offload, and external worker architecture

When should you choose Azure App Service over alternatives?

Azure App Service: Deploy a Web App the Easy Way isn't always the right answer. Understanding trade-offs prevents costly migrations later. Compare it against the alternatives I regularly evaluate for clients considering cloud hosting services.

  • Choose Azure App Service when: You need managed PaaS with built-in scaling, staging slots, and Microsoft ecosystem integration. Ideal for B2B SaaS, legal-tech portals, and applications requiring enterprise compliance certifications.
  • Choose a VPS (DigitalOcean/Linode/Hetzner) when: Budget is under Rs 3,000/month (~USD 22), you need root access for custom system packages, or your team has strong Linux sysadmin skills. Better for hobby projects and early-stage startups validating product-market fit.
  • Choose AWS Lambda/Vapor when: Traffic is highly spiky with long idle periods, and you want true serverless billing. Trade-off: cold starts hurt user-facing latency for Laravel apps.
  • Choose Kubernetes (AKS/EKS) when: You operate 10+ microservices with dedicated DevOps staff. Overkill for single monolithic Laravel applications unless you're already standardized on K8s.

For Nepal-based businesses serving international clients, Azure's global datacenter network provides lower latency than Kathmandu-hosted servers. However, if your primary users are domestic and budget is constrained, a well-configured VPS with proper caching often delivers better price-performance. The decision hinges on operational capacity, not just raw specs.

Next Steps for Production Azure Deployments

Azure App Service: Deploy a Web App the Easy Way becomes genuinely easy once you establish the CLI-first workflow, enforce staging slot discipline, and handle Laravel's filesystem requirements correctly. Start with the B1 Linux plan, automate provisioning via scripts, and integrate GitHub Actions from day one. Resist the temptation to click through the portal for production setups; the time saved upfront costs hours of debugging later.

If you're evaluating Azure for a Laravel application, legal-tech platform, or eCommerce system and want to discuss architecture decisions specific to your workload, reach out to discuss your deployment strategy. I help teams navigate cloud infrastructure choices that balance cost, reliability, and maintainability for real-world production systems.

Frequently Asked Questions

Azure App Service is a managed PaaS for hosting web applications, REST APIs, and mobile backends without managing servers. It handles OS patching, load balancing, and auto-scaling automatically.

Basic B1 tier costs approximately USD 55 monthly or NPR 7,300 plus VAT. Production workloads typically require Standard S1 at USD 80 monthly or NPR 10,600 for custom domains and SSL.

Azure supports PHP 8.2, 8.3, and 8.4 as of 2026. Laravel 12 requires minimum PHP 8.2. Always pin your specific minor version in configuration to prevent unexpected runtime upgrades during maintenance windows.

Configure GitHub Actions or Azure DevOps pipelines targeting the App Service. Set APP_ENV, APP_KEY, and database credentials in Configuration settings. Run composer install and php artisan migrate during deployment. Point the document root to /public directory via startup commands or virtual application settings.

Yes, using the Linux-based App Service plan with PHP 8.3 or 8.4. Use Azure Database for MySQL Flexible Server instead of local storage. Configure Redis Cache for object caching. Expect higher costs than shared hosting, starting around NPR 8,000 monthly for production-grade performance and reliability.

Linux plans run native PHP-FPM containers with better performance for Laravel and Symfony. Windows plans use IIS with FastCGI, suitable only for legacy .NET or older PHP applications. Linux is recommended for all modern PHP frameworks due to lower overhead and closer parity with local development environments.

Store secrets in Azure Key Vault and reference them via Key Vault References in App Service Configuration. This avoids plaintext values in portal settings. For Laravel, map these references to standard env variable names so framework configuration remains unchanged across environments.

The document root defaults to /wwwroot instead of /wwwroot/public. Add a custom startup script containing nginx -g daemon off; or configure virtual applications in portal settings. Also verify .htaccess or nginx.conf rewrites exist for front-controller routing. Missing index.php fallback causes most post-deployment 404s.

App Service offers simpler configuration and integrated monitoring but less infrastructure control. Elastic Beanstalk provides deeper customization at the cost of operational complexity. For Nepal-based teams managing multiple client sites, App Service reduces DevOps overhead significantly while maintaining adequate scaling for typical SMB traffic patterns.

Use Azure Database for MySQL Flexible Server for Laravel compatibility and cost efficiency. PostgreSQL Flexible Server works equally well. Avoid Azure SQL unless you have specific Microsoft ecosystem requirements. Both MySQL and PostgreSQL options support read replicas and point-in-time recovery essential for production eCommerce and legal-tech portals.

Add your custom domain in Custom Domains blade, validate ownership via CNAME or TXT record, then bind a free App Service Managed Certificate or upload your own. Force HTTPS redirect in Configuration settings. Let's Encrypt certificates auto-renew when using managed certificates, eliminating manual renewal tasks for client projects.

Insufficient instance count during traffic spikes, missing application-level caching, unoptimized database queries, and cold starts on lower tiers. Enable Application Insights to identify slow requests. Add Redis Cache for session and query result caching. Scale out horizontally rather than vertically for consistent response times under variable load conditions.

Check Log Stream in portal for real-time error output. Review application logs in /home/LogFiles for Laravel stack traces. Verify PHP version matches composer.json requirements. Confirm database connectivity and firewall rules. Common causes include missing extensions, incorrect file permissions on storage directory, and misconfigured environment variables after deployment.

Yes, with proper architecture. Use Premium V3 tier for CPU-intensive operations like cart calculations and payment processing. Implement horizontal auto-scaling based on HTTP queue length. Offload media to Azure Blob Storage with CDN. Separate background jobs to Azure Functions or dedicated worker instances. I have deployed WooCommerce and custom Laravel carts handling thousands of daily orders using this pattern.

Use deployment slots to stage changes in an isolated environment. Validate functionality and warm up caches before swapping slots into production. Configure auto-swap with health checks to rollback automatically if errors exceed threshold. This mirrors the symlinked release strategy I use with Deployer 7 on traditional Linux servers, providing safe production updates without user-facing interruptions.

Share this article

Quick Contact Options
Choose how you want to connect me: