Tutorial: Build a PHP and MySQL app in Azure App Service

Azure App Service provides a highly scalable, self-patching web hosting service using the Windows operating system. This tutorial shows how to create a PHP app in Azure and connect it to a MySQL database. When you're finished, you'll have a Laravel app running on Azure App Service on Windows.

Azure App Service provides a highly scalable, self-patching web hosting service using the Linux operating system. This tutorial shows how to create a PHP app in Azure and connect it to a MySQL database. When you're finished, you'll have a Laravel app running on Azure App Service on Linux.

Screenshot of the Azure app example titled Task List showing new tasks added.

In this tutorial, you learn how to:

  • Create a MySQL database in Azure
  • Connect a PHP app to MySQL
  • Deploy the app to Azure
  • Update the data model and redeploy the app
  • Stream diagnostic logs from Azure
  • Manage the app in the Azure portal

If you don't have an Azure subscription, create an Azure free account before you begin.

Prerequisites

To complete this tutorial:

Prepare local MySQL

In this step, you create a database in your local MySQL server for your use in this tutorial.

Connect to local MySQL server

In a local terminal window, connect to your local MySQL server. You can use this terminal window to run all the commands in this tutorial.

mysql -u root -p

If you're prompted for a password, enter the password for the root account. If you don't remember your root account password, see MySQL: How to Reset the Root Password.

If your command runs successfully, then your MySQL server is running. If not, ensure that your local MySQL server is started by following the MySQL post-installation steps.

Create a database locally

  1. At the mysql prompt, create a database.

    CREATE DATABASE sampledb;
    
  2. Exit your server connection by typing quit.

    quit
    

Create a PHP app locally

In this step, you get a Laravel sample application, configure its database connection, and run it locally.

Clone the sample

  1. cd to a working directory.

  2. Clone the sample repository and change to the repository root.

    git clone https://github.com/Azure-Samples/laravel-tasks
    cd laravel-tasks
    
  3. Install the required packages.

    composer install
    

Configure MySQL connection

In the repository root, create a file named .env. Copy the following variables into the .env file. Replace the <root_password> placeholder with the MySQL root user's password.

APP_ENV=local
APP_DEBUG=true
APP_KEY=

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_DATABASE=sampledb
DB_USERNAME=root
DB_PASSWORD=<root_password>

For information on how Laravel uses the .env file, see Laravel Environment Configuration.

Run the sample locally

  1. Run Laravel database migrations to create the tables the application needs. To see which tables are created in the migrations, look in the database/migrations directory in the Git repository.

    php artisan migrate
    
  2. Generate a new Laravel application key.

    php artisan key:generate
    
  3. Run the application.

    php artisan serve
    
  4. Go to http://localhost:8000 in a browser. Add a few tasks in the page.

    PHP connects successfully to MySQL

  5. To stop PHP, enter Ctrl + C in the terminal.

Deploy Laravel sample to App Service

Deploy sample code

  1. In the root directory of the respository, add a file called .deployment. This file tells App Service to run a custom deployment script during build automation. Copy the following text into it as its content:

    [config]
    command = bash deploy.sh  
    

    Note

    The deployment process installs Composer packages at the end. App Service on Windows does not run these automations during default deployment, so this sample repository has two additional files in its root directory to enable it:

    • deploy.sh - The custom deployment script. If you review the file, you see that it runs php composer.phar install.
    • composer.phar - The Composer package manager.

    You can use this approach to add any step to your Git-based or ZIP deployment to App Service. For more information, see Custom Deployment Script.

  1. From the command line, sign in to Azure using the az login command.

    az login
    
  2. Deploy the code in your local folder using the az webapp up command. Replace <app-name> with a unique name for your app.

    az webapp up --resource-group myResourceGroup --name <app-name> --location "West Europe" --sku FREE --runtime "php|7.4" --os-type=windows
    
    az webapp up --resource-group myResourceGroup --name <app-name> --location "West Europe" --sku FREE --runtime "php|7.4" --os-type=linux
    

    Note

    The az webapp up command does the following actions:

    • Create a default resource group.

    • Create a default App Service plan.

    • Create an app with the specified name.

    • Zip deploy all files from the current working directory, with build automation enabled.

    • Cache the parameters locally in the .azure/config file so that you don't need to specify them again when deploying later with az webapp up or other az webapp commands from the project folder. The cached values are used automatically by default.

Configure Laravel environment variables

Laravel needs an application key in App Service. You can configure it with app settings.

  1. Use php artisan to generate a new application key without saving it to .env.

    php artisan key:generate --show
    
  2. Set the application key in the App Service app by using the az webapp config appsettings set command. Replace the placeholders <app-name> and <outputofphpartisankey:generate>.

    az webapp config appsettings set --settings APP_KEY="<output_of_php_artisan_key:generate>" APP_DEBUG="true"
    

    APP_DEBUG="true" tells Laravel to return debugging information when the deployed app encounters errors. When running a production application, set it to false, which is more secure.

Set the virtual application path

Set the virtual application path for the app. This step is required because the Laravel application lifecycle begins in the public directory instead of the application's root directory. Other PHP frameworks whose lifecycle start in the root directory can work without manual configuration of the virtual application path.

Set the virtual application path by using the az resource update command. Replace the <app-name> placeholder.

az resource update --name web --resource-group myResourceGroup --namespace Microsoft.Web --resource-type config --parent sites/<app_name> --set properties.virtualApplications[0].physicalPath="site\wwwroot\public" --api-version 2015-06-01

By default, Azure App Service points the root virtual application path (/) to the root directory of the deployed application files (sites\wwwroot).

Laravel application lifecycle begins in the public directory instead of the application's root directory. The default PHP Docker image for App Service uses Apache, and it doesn't let you customize the DocumentRoot for Laravel. But you can use .htaccess to rewrite all requests to point to /public instead of the root directory. In the repository root, an .htaccess is added already for this purpose. With it, your Laravel application is ready to be deployed.

For more information, see Change site root.

If you browse to https://<app-name>.azurewebsites.net now and see a Whoops, looks like something went wrong message, then you have configured your App Service app properly and it's running in Azure. It just doesn't have database connectivity yet. In the next step, you create a MySQL database in Azure Database for MySQL.

Create MySQL in Azure

  1. Create a MySQL server in Azure with the az mysql server create command.

    In the following command, substitute a unique server name for the <mysql-server-name> placeholder, a user name for the <admin-user>, and a password for the <admin-password> placeholder. The server name is used as part of your MySQL endpoint (https://<mysql-server-name>.mysql.database.azure.com), so the name needs to be unique across all servers in Azure. For details on selecting MySQL DB SKU, see Create an Azure Database for MySQL server.

    az mysql server create --resource-group myResourceGroup --name <mysql-server-name> --location "West Europe" --admin-user <admin-user> --admin-password <admin-password> --sku-name B_Gen5_1
    
  2. Create a database called sampledb by using the az mysql db create command.

    az mysql db create --resource-group myResourceGroup --server-name <mysql-server-name> --name sampledb    
    

Connect the app to the database

Configure the connection between your app and the SQL database by using the az webapp connection create mysql command. --target-resource-group is the resource group that contains the MySQL database.

az webapp connection create mysql --resource-group myResourceGroup --name <app-name> --target-resource-group myResourceGroup --server <mysql-server-name> --database sampledb --connection my_laravel_db --client-type php
az webapp connection create mysql --resource-group myResourceGroup --name <app-name> --target-resource-group myResourceGroup --server <mysql-server-name> --database sampledb --connection my_laravel_db

When prompted, provide the administrator username and password for the MySQL database.

Note

The CLI command does everything the app needs to successfully connect to the database, including:

  • In your App Service app, adds six app settings with the names AZURE_MYSQL_<setting>, which your code can use for its database connection. If the app setting names are already in use, the AZURE_MYSQL_<connection-name>_<setting> format is used instead.
  • In your MySQL database server, allows Azure services to access the MySQL database server.

Generate the database schema

  1. Allow access to the Azure database from your local computer by using the az mysql server firewall-rule create and replacing <your-ip-address> with your local IPv4 IP address.

    az mysql server firewall-rule create --name AllowLocalClient --server <mysql-server-name> --resource-group myResourceGroup --start-ip-address=<your-ip-address> --end-ip-address=<your-ip-address>
    

    Tip

    Once the firewall rule for your local computer is enabled, you can connect to the server like any MySQL server with the mysql client. For example:

    mysql -u <admin-user>@<mysql-server-name> -h <mysql-server-name>.mysql.database.azure.com -P 3306 -p
    
  2. Generate the environment variables from the service connector you created earlier by running the az webapp connection list-configuration command.

    $Settings = az webapp connection list-configuration --resource-group myResourceGroup --name <app-name> --connection my_laravel_db --query configurations | ConvertFrom-Json
    foreach ($s in $Settings) { New-Item -Path Env:$($s.name) -Value $s.value}
    

    Tip

    These commands are equivalent to setting the database variables manually like this:

    New-Item -Path Env:AZURE_MYSQL_DBNAME -Value ...
    New-Item -Path Env:AZURE_MYSQL_HOST -Value ...
    New-Item -Path Env:AZURE_MYSQL_PORT -Value ...
    New-Item -Path Env:AZURE_MYSQL_FLAG -Value ...
    New-Item -Path Env:AZURE_MYSQL_USERNAME -Value ...
    New-Item -Path Env:AZURE_MYSQL_PASSWORD -Value ...
    
    export $(az webapp connection list-configuration --resource-group myResourceGroup --name <app-name> --connection my_laravel_db --query "configurations[].[name,value] | [*].join('=',@)" --output tsv)
    

    Tip

    The JMESPath query in --query and the --output tsv formatting let you feed the output directly into the export command. It's equivalent to setting the database variables manually like this:

    export AZURE_MYSQL_DBNAME=...
    export AZURE_MYSQL_HOST=...
    export AZURE_MYSQL_PORT=...
    export AZURE_MYSQL_FLAG=...
    export AZURE_MYSQL_USERNAME=...
    export AZURE_MYSQL_PASSWORD=...
    
  3. Open config/database.php and find the mysql section. It's already set up to retrieve connection secrets from environment variables.

    'mysql' => [
        'driver'    => 'mysql',
        'url' => env('DATABASE_URL'),
        'host' => env('DB_HOST', '127.0.0.1'),
        'port' => env('DB_PORT', '3306'),
        'database'  => env('DB_DATABASE', 'forge'),
        'username'  => env('DB_USERNAME', 'forge'),
        'password'  => env('DB_PASSWORD', ''),
        ...
    ],
    

    Change the default environment variables to the ones that the service connector created:

    'mysql' => [
        'driver' => 'mysql',
        'url' => env('DATABASE_URL'),
        'host' => env('AZURE_MYSQL_HOST', '127.0.0.1'),
        'port' => env('AZURE_MYSQL_PORT', '3306'),
        'database' => env('AZURE_MYSQL_DBNAME', 'forge'),
        'username' => env('AZURE_MYSQL_USERNAME', 'forge'),
        'password' => env('AZURE_MYSQL_PASSWORD', ''),
        ...
    ],
    

    Tip

    PHP uses the getenv method to access the settings. the Laravel code uses an env wrapper over the PHP getenv.

  4. By default, Azure Database for MySQL enforces TLS connections from clients. To connect to your MySQL database in Azure, you must use the .pem certificate supplied by Azure Database for MySQL. The certificate BaltimoreCyberTrustRoot.crt.pem is provided in the sample repository for convenience in this tutorial. At the bottom of the mysql section in config/database.php, change the options parameter to the following code:

    'options' => extension_loaded('pdo_mysql') ? array_filter([
        PDO::MYSQL_ATTR_SSL_KEY    => '/ssl/BaltimoreCyberTrustRoot.crt.pem',
    ]) : [],
    
  5. Your sample app is now configured to connect to the Azure MySQL database. Run Laravel database migrations again to create the tables and run the sample app.

    php artisan migrate
    php artisan serve
    
  6. Go to http://localhost:8000. If the page loads without errors, the PHP application is connecting to the MySQL database in Azure.

  7. Add a few tasks in the page.

    PHP connects successfully to Azure Database for MySQL

  8. To stop PHP, enter Ctrl + C in the terminal.

Deploy changes to Azure

  1. Deploy your code changes by running az webapp up again.

    az webapp up --os-type=windows
    
    az webapp up --runtime "php|7.4" --os-type=linux
    

    Note

    --runtime is still needed for deployment with az webapp up. Otherwise, the runtime is detected to be Node.js due to the presence of package.json.

  2. Browse to http://<app-name>.azurewebsites.net and add a few tasks to the list.

    Screenshot of the Azure app example titled Task List showing new tasks added.

Congratulations, you're running a data-driven PHP app in Azure App Service.

Stream diagnostic logs

While the PHP application runs in Azure App Service, you can get the console logs piped to your terminal. That way, you can get the same diagnostic messages to help you debug application errors.

To start log streaming, use the az webapp log tail command.

az webapp log tail

Once log streaming has started, refresh the Azure app in the browser to get some web traffic. You can now see console logs piped to the terminal. If you don't see console logs immediately, check again in 30 seconds.

To stop log streaming at any time, enter Ctrl+C.

Note

You can also inspect the log files from the browser at https://<app-name>.scm.azurewebsites.net/api/logs/docker.

Tip

A PHP application can use the standard error_log() to output to the console. The sample application uses this approach in app/Http/routes.php.

As a web framework, Laravel uses Monolog as the logging provider. To see how to get Monolog to output messages to the console, see PHP: How to use monolog to log to console (php://out).

Clean up resources

In the preceding steps, you created Azure resources in a resource group. If you don't expect to need these resources in the future, delete the resource group by running the following command in the Cloud Shell:

az group delete --name myResourceGroup

This command may take a minute to run.

Next steps

In this tutorial, you learned how to:

  • Create a MySQL database in Azure
  • Connect a PHP app to MySQL
  • Deploy the app to Azure
  • Update the data model and redeploy the app
  • Stream diagnostic logs from Azure
  • Manage the app in the Azure portal

Advance to the next tutorial to learn how to map a custom DNS name to the app.

Or, check out other resources: