Build a PHP and MySQL web app in Azure

Azure Web Apps provides a highly scalable, self-patching web hosting service. This tutorial shows how to create a PHP web 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 Web Apps.

PHP app running in Azure App Service

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

Prerequisites

To complete this tutorial:

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

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 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, make sure that your local MySQL server is started by following the MySQL post-installation steps.

Create a database locally

At the mysql prompt, create a database.

CREATE DATABASE sampledb;

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

In the terminal window, cd to a working directory.

Run the following command to clone the sample repository.

git clone https://github.com/Azure-Samples/laravel-tasks

cd to your cloned directory. Install the required packages.

cd laravel-tasks
composer install

Configure MySQL connection

In the repository root, create a text 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

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

Generate a new Laravel application key.

php artisan key:generate

Run the application.

php artisan serve

Navigate to http://localhost:8000 in a browser. Add a few tasks in the page.

PHP connects successfully to MySQL

To stop the PHP server, type Ctrl + C in the terminal.

Launch Azure Cloud Shell

The Azure Cloud Shell is a free Bash shell that you can run directly within the Azure portal. It has the Azure CLI preinstalled and configured to use with your account. Click the Cloud Shell button on the menu in the upper-right of the Azure portal.

Cloud Shell

The button launches an interactive shell that you can use to run the steps in this topic:

Screenshot showing the Cloud Shell window in the portal

Create MySQL in Azure

In this step, you create a MySQL database in Azure Database for MySQL (Preview). Later, you configure the PHP application to connect to this database.

Create a resource group

In the Cloud Shell, create a resource group with the az group create command.

A resource group is a logical container into which Azure resources like web apps, databases, and storage accounts are deployed and managed.

The following example creates a resource group named myResourceGroup in the West Europe location.

az group create --name myResourceGroup --location "West Europe"

You generally create your resource group and the resources in a region near you. To see all supported locations for Azure Web Apps, run the az appservice list-locations command.

Create a MySQL server

In the Cloud Shell, create a server in Azure Database for MySQL (Preview) with the az mysql server create command.

In the following command, substitute your MySQL server name where you see the <mysql_server_name> placeholder (valid characters are a-z, 0-9, and -). This name is part of the MySQL server's hostname (<mysql_server_name>.database.windows.net), it needs to be globally unique.

az mysql server create --name <mysql_server_name> --resource-group myResourceGroup --location "North Europe" --admin-user adminuser --admin-password MySQLAzure2017
Note

Since there are several credentials to think about in this tutorial, to avoid confusion, --admin-user and --admin-password are set to dummy values. In a production environment, follow security best practices when choosing a good username and password for your MySQL server in Azure.

When the MySQL server is created, the Azure CLI shows information similar to the following example:

{
  "administratorLogin": "adminuser",
  "administratorLoginPassword": null,
  "fullyQualifiedDomainName": "<mysql_server_name>.database.windows.net",
  "id": "/subscriptions/00000000-0000-0000-0000-000000000000/resourceGroups/myResourceGroup/providers/Microsoft.DBforMySQL/servers/<mysql_server_name>",
  "location": "northeurope",
  "name": "<mysql_server_name>",
  "resourceGroup": "myResourceGroup",
  ...
}

Configure server firewall

In the Cloud Shell, create a firewall rule for your MySQL server to allow client connections by using the az mysql server firewall-rule create command.

az mysql server firewall-rule create --name allIPs --server <mysql_server_name> --resource-group myResourceGroup --start-ip-address 0.0.0.0 --end-ip-address 255.255.255.255
Note

Azure Database for MySQL (Preview) doesn't currently limit connections only to Azure services. As IP addresses in Azure are dynamically assigned, it is better to enable all IP addresses. The service is in preview. Better methods for securing your database are planned.

Connect to production MySQL server locally

In the local terminal window, connect to the MySQL server in Azure. Use the value you specified previously for <mysql_server_name>. When prompted for a password, use MySQLAzure2017, which you specified when you created the database in Azure.

mysql -u adminuser@<mysql_server_name> -h <mysql_server_name>.database.windows.net -P 3306 -p

Create a production database

At the mysql prompt, create a database.

CREATE DATABASE sampledb;

Create a user with permissions

Create a database user called phpappuser and give it all privileges in the sampledb database. Again, for simplicity of the tutorial, use MySQLAzure2017 as the password.

CREATE USER 'phpappuser' IDENTIFIED BY 'MySQLAzure2017'; 
GRANT ALL PRIVILEGES ON sampledb.* TO 'phpappuser';

Exit the server connection by typing quit.

quit

Connect app to Azure MySQL

In this step, you connect the PHP application to the MySQL database you created in Azure Database for MySQL (Preview).

Configure the database connection

In the repository root, create an .env.production file and copy the following variables into it. Replace the placeholder <mysql_server_name> in both DB_HOST and DB_USERNAME.

APP_ENV=production
APP_DEBUG=true
APP_KEY=

DB_CONNECTION=mysql
DB_HOST=<mysql_server_name>.database.windows.net
DB_DATABASE=sampledb
DB_USERNAME=phpappuser@<mysql_server_name>
DB_PASSWORD=MySQLAzure2017
MYSQL_SSL=true

Save the changes.

Tip

To secure your MySQL connection information, this file is already excluded from the Git repository (See .gitignore in the repository root). Later, you learn how to configure environment variables in App Service to connect to your database in Azure Database for MySQL (Preview). With environment variables, you don't need the .env file in App Service.

Configure SSL certificate

By default, Azure Database for MySQL enforces SSL connections from clients. To connect to your MySQL database in Azure, you must use a .pem SSL certificate.

Open config/database.php and add the sslmode and options parameters to connections.mysql, as shown in the following code.

'mysql' => [
    ...
    'sslmode' => env('DB_SSLMODE', 'prefer'),
    'options' => (env('MYSQL_SSL')) ? [
        PDO::MYSQL_ATTR_SSL_KEY    => '/ssl/certificate.pem', 
    ] : []
],

To learn how to generate this certificate.pem, see Configure SSL connectivity in your application to securely connect to Azure Database for MySQL.

Tip

The path /ssl/certificate.pem points to an existing certificate.pem file in the Git repository. This file is provided for convenience in this tutorial. For best practice, you should not commit your .pem certificates into source control.

Test the application locally

Run Laravel database migrations with .env.production as the environment file to create the tables in your MySQL database in Azure Database for MySQL (Preview). Remember that .env.production has the connection information to your MySQL database in Azure.

php artisan migrate --env=production --force

.env.production doesn't have a valid application key yet. Generate a new one for it in the terminal.

php artisan key:generate --env=production --force

Run the sample application with .env.production as the environment file.

php artisan serve --env=production

Navigate to http://localhost:8000. If the page loads without errors, the PHP application is connecting to the MySQL database in Azure.

Add a few tasks in the page.

PHP connects successfully to Azure Database for MySQL (Preview)

To stop PHP, type Ctrl + C in the terminal.

Commit your changes

Run the following Git commands to commit your changes:

git add .
git commit -m "database.php updates"

Your app is ready to be deployed.

Deploy to Azure

In this step, you deploy the MySQL-connected PHP application to Azure App Service.

Create an App Service plan

In the Cloud Shell, create an App Service plan with the az appservice plan create command.

An App Service plan specifies the location, size, and features of the web server farm that hosts your app. You can save money when hosting multiple apps by configuring the web apps to share a single App Service plan.

App Service plans define:

  • Region (for example: North Europe, East US, or Southeast Asia)
  • Instance size (small, medium, or large)
  • Scale count (1 to 20 instances)
  • SKU (Free, Shared, Basic, Standard, or Premium)

The following example creates an App Service plan named myAppServicePlan in the Free pricing tier:

az appservice plan create --name myAppServicePlan --resource-group myResourceGroup --sku FREE

When the App Service plan has been created, the Azure CLI shows information similar to the following example:

{ 
  "adminSiteName": null,
  "appServicePlanName": "myAppServicePlan",
  "geoRegion": "West Europe",
  "hostingEnvironmentProfile": null,
  "id": "/subscriptions/0000-0000/resourceGroups/myResourceGroup/providers/Microsoft.Web/serverfarms/myAppServicePlan",
  "kind": "app",
  "location": "West Europe",
  "maximumNumberOfWorkers": 1,
  "name": "myAppServicePlan",
  < JSON data removed for brevity. >
  "targetWorkerSizeId": 0,
  "type": "Microsoft.Web/serverfarms",
  "workerTierName": null
} 

Create a web app

In the Cloud Shell, create a web app in the myAppServicePlan App Service plan with the az webapp create command.

The web app provides a hosting space for your code and provides a URL to view the deployed app.

In the following command, replace <app_name> with a unique name (valid characters are a-z, 0-9, and -). If <app_name> is not unique, you get the error message "Website with given name <app_name> already exists." The default URL of the web app is https://<app_name>.azurewebsites.net.

az webapp create --name <app_name> --resource-group myResourceGroup --plan myAppServicePlan --deployment-local-git

When the web app has been created, the Azure CLI shows information similar to the following example:

{
  "availabilityState": "Normal",
  "clientAffinityEnabled": true,
  "clientCertEnabled": false,
  "cloningInfo": null,
  "containerSize": 0,
  "dailyMemoryTimeQuota": 0,
  "defaultHostName": "<app_name>.azurewebsites.net",
  "enabled": true,
  "enabledHostNames": [
    "<app_name>.azurewebsites.net",
    "<app_name>.scm.azurewebsites.net"
  ],
  "gatewaySiteName": null,
  "hostNameSslStates": [
    {
      "hostType": "Standard",
      "name": "<app_name>.azurewebsites.net",
      "sslState": "Disabled",
      "thumbprint": null,
      "toUpdate": null,
      "virtualIp": null
    }
    < JSON data removed for brevity. >
}

Browse to the site to see your newly created web app.

http://<app_name>.azurewebsites.net

Set the PHP version

In the Cloud Shell, set the PHP version that the application requires by using the az webapp config set command.

The following command sets the PHP version to 7.0.

az webapp config set --name <app_name> --resource-group myResourceGroup --php-version 7.0

Configure database settings

As pointed out previously, you can connect to your Azure MySQL database using environment variables in App Service.

In the Cloud Shell, you set environment variables as app settings by using the az webapp config appsettings set command.

The following command configures the app settings DB_HOST, DB_DATABASE, DB_USERNAME, and DB_PASSWORD. Replace the placeholders <appname> and <mysql_server_name>.

az webapp config appsettings set --name <app_name> --resource-group myResourceGroup --settings DB_HOST="<mysql_server_name>.database.windows.net" DB_DATABASE="sampledb" DB_USERNAME="phpappuser@<mysql_server_name>" DB_PASSWORD="MySQLAzure2017" MYSQL_SSL="true"

You can use the PHP getenv method to access the settings. the Laravel code uses an env wrapper over the PHP getenv. For example, the MySQL configuration in config/database.php looks like the following code:

'mysql' => [
    'driver'    => 'mysql',
    'host'      => env('DB_HOST', 'localhost'),
    'database'  => env('DB_DATABASE', 'forge'),
    'username'  => env('DB_USERNAME', 'forge'),
    'password'  => env('DB_PASSWORD', ''),
    ...
],

Configure Laravel environment variables

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

In the local terminal window, use php artisan to generate a new application key without saving it to .env.

php artisan key:generate --show

In the Cloud Shell, set the application key in the App Service web app by using the az webapp config appsettings set command. Replace the placeholders <appname> and <outputofphpartisankey:generate>.

az webapp config appsettings set --name <app_name> --resource-group myResourceGroup --settings APP_KEY="<output_of_php_artisan_key:generate>" APP_DEBUG="true"

APP_DEBUG="true" tells Laravel to return debugging information when the deployed web 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 web 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.

In the Cloud Shell, set the virtual application path by using the az resource update command. Replace the <appname> 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).

Configure a deployment user

In the Cloud Shell, create deployment credentials with the az webapp deployment user set command.

A deployment user is required for FTP and local Git deployment to a web app. The user name and password are account level. They are different from your Azure subscription credentials.

In the following command, replace <username> and <password> with a new user name and password. The user name must be unique. The password must be at least eight characters long, with two of the following three elements: letters, numbers, symbols.

az webapp deployment user set --user-name <username> --password <password>

If you get a 'Conflict'. Details: 409 error, change the username. If you get a 'Bad Request'. Details: 400 error, use a stronger password.

You create this deployment user only once; you can use it for all your Azure deployments.

Note

Record the user name and password. You need them to deploy the web app later.

Configure local Git deployment

Configure local Git deployment to the web app with the az webapp deployment source config-local-git command.

App Service supports several ways to deploy content to a web app, such as FTP, local Git, GitHub, Visual Studio Team Services, and Bitbucket. For this quickstart, you deploy by using local Git. That means you deploy by using a Git command to push from a local repository to a repository in Azure.

In the following command, replace <app_name> with your web app's name.

az webapp deployment source config-local-git --name <app_name> --resource-group myResourceGroup --query url --output tsv

The output has the following format:

https://<username>@<app_name>.scm.azurewebsites.net:443/<app_name>.git

The <username> is the deployment user that you created in a previous step.

Copy the URI shown; you'll use it in the next step.

Push to Azure from Git

In the local terminal window, add an Azure remote to your local Git repository.

git remote add azure <paste_copied_url_here>

Push to the Azure remote to deploy the PHP application. You are prompted for the password you supplied earlier as part of the creation of the deployment user.

git push azure master

During deployment, Azure App Service communicates its progress with Git.

Counting objects: 3, done.
Delta compression using up to 8 threads.
Compressing objects: 100% (3/3), done.
Writing objects: 100% (3/3), 291 bytes | 0 bytes/s, done.
Total 3 (delta 2), reused 0 (delta 0)
remote: Updating branch 'master'.
remote: Updating submodules.
remote: Preparing deployment for commit id 'a5e076db9c'.
remote: Running custom deployment command...
remote: Running deployment command...
...
< Output has been truncated for readability >
Note

You may notice that the deployment process installs Composer packages at the end. App Service does not run these automations during default deployment, so this sample repository has three additional files in its root directory to enable it:

  • .deployment - This file tells App Service to run bash deploy.sh as the custom deployment script.
  • deploy.sh - The custom deployment script. If you review the file, you will see that it runs php composer.phar install after npm install.
  • composer.phar - The Composer package manager.

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

Browse to the Azure web app

Browse to http://<app_name>.azurewebsites.net and add a few tasks to the list.

PHP app running in Azure App Service

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

Update model locally and redeploy

In this step, you make a simple change to the task data model and the webapp, and then publish the update to Azure.

For the tasks scenario, you modify the application so that you can mark a task as complete.

Add a column

In the local terminal window, navigate to the root of the Git repository.

Generate a new database migration for the tasks table:

php artisan make:migration add_complete_column --table=tasks

This command shows you the name of the migration file that's generated. Find this file in database/migrations and open it.

Replace the up method with the following code:

public function up()
{
    Schema::table('tasks', function (Blueprint $table) {
        $table->boolean('complete')->default(False);
    });
}

The preceding code adds a boolean column in the tasks table called complete.

Replace the down method with the following code for the rollback action:

public function down()
{
    Schema::table('tasks', function (Blueprint $table) {
        $table->dropColumn('complete');
    });
}

In the local terminal window, run Laravel database migrations to make the change in the local database.

php artisan migrate

Based on the Laravel naming convention, the model Task (see app/Task.php) maps to the tasks table by default.

Update application logic

Open the routes/web.php file. The application defines its routes and business logic here.

At the end of the file, add a route with the following code:

/**
 * Toggle Task completeness
 */
Route::post('/task/{id}', function ($id) {
    error_log('INFO: post /task/'.$id);
    $task = Task::findOrFail($id);

    $task->complete = !$task->complete;
    $task->save();

    return redirect('/');
});

The preceding code makes a simple update to the data model by toggling the value of complete.

Update the view

Open the resources/views/tasks.blade.php file. Search for the <tr> opening tag and replace it with:

<tr class="{{ $task->complete ? 'success' : 'active' }}" >

The preceding code changes the row color depending on whether the task is complete.

In the next line, you have the following code:

<td class="table-text"><div>{{ $task->name }}</div></td>

Replace the entire line with the following code:

<td>
    <form action="{{ url('task/'.$task->id) }}" method="POST">
        {{ csrf_field() }}

        <button type="submit" class="btn btn-xs">
            <i class="fa {{$task->complete ? 'fa-check-square-o' : 'fa-square-o'}}"></i>
        </button>
        {{ $task->name }}
    </form>
</td>

The preceding code adds the submit button that references the route that you defined earlier.

Test the changes locally

In the local terminal window, run the development server from the root directory of the Git repository.

php artisan serve

To see the task status change, navigate to http://localhost:8000 and select the checkbox.

Added check box to task

To stop PHP, type Ctrl + C in the terminal.

Publish changes to Azure

In the local terminal window, run Laravel database migrations with the production connection string to make the change in the Azure database.

php artisan migrate --env=production --force

Commit all the changes in Git, and then push the code changes to Azure.

git add .
git commit -m "added complete checkbox"
git push azure master

Once the git push is complete, navigate to the Azure web app and test the new functionality.

Model and database changes published to Azure

If you added any tasks, they are retained in the database. Updates to the data schema leave existing data intact.

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 in the Cloud Shell.

az webapp log tail --name <app_name> --resource-group myResourceGroup

Once log streaming has started, refresh the Azure web 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 anytime, type Ctrl+C.

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).

Manage the Azure web app

Go to the Azure portal to manage the web app you created.

From the left menu, click App Services, and then click the name of your Azure web app.

Portal navigation to Azure web app

You see your web app's Overview page. Here, you can perform basic management tasks like stop, start, restart, browse, and delete.

The left menu provides pages for configuring your app.

App Service page in Azure portal

Clean up resources

To clean up your resources, run the following command:

az group delete --name myResourceGroup

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 a web app.