This article will cover how to deploy Keystone to Azure using Platform-as-a-Service (PaaS).
You will need an Azure account for this, you can sign up for a free trial if you don't already have one.
There are three resources in Azure that are required to run Keystone in a PaaS model, AppService to host the Keystone web application, Storage to store images/uploaded assets, and a managed Postgres database.
- Creating Resoueces
- Storing files and images with Azure Storage
- Automated Deploymnets
- Azure AD B2C for user management
In this section we'll use the Azure Portal to create the required resources to host Keystone.
-
Navigate to the Azure Portal
-
Click Create a resource and search for Resource group from the provided search box
-
Provide a name for your Resource Group,
my-keystone-app
, and select a region -
Click Review + create then Create
-
Navigate to the Resource Group once it's created, click Create resources and search for Web App
-
Ensure the Subscription and Resource Group are correct, then provide the following configuration for the app:
- Name -
my-keystone-app
- Publish -
Code
- Runtime stack -
Node 14 LTS
- Operating System -
Linux
- Region - Select an appropriate region
- Name -
-
Use the App Service Plan to select the appropriate Sku and size for the level fo scale your app will need (refer to the Azure docs for more information on the various Sku and sizes)
-
Click Review + create then Create
-
Navigate back to the Resource Group and click Create then search for Storage account and click Create
-
Ensure the Subscription and Resource Group are correct, then provide the following configuration for the storage account:
- Name -
mykeystoneapp
- Region - Select an appropriate region
- Performance -
Standard
- Redundancy - Select the appropriate level of redundancy for your files
- Name -
-
Click Review + create then Create
-
Navigate back to the Resource Group and click Create then search for Azure Database for PostgreSQL and click Create
-
Select Single server for the service type
-
Ensure the Subscription and Resource Group are correct, then provide the following configuration for the storage account:
- Name -
my-keystone-db
- Data source -
None
(unless you're wanting to import from a backup) - Location - Select an appropriate region
- Version -
11
- Compute + storage - Select an appropriate scale for your requirements (Basic is adequate for many Keystone workloads)
- Name -
-
Enter a username and password for the Administrator account, click Review + create then Create
Once all the resources are created, you will need to get the connection information for the Postgres and Storage account to the Web App (App Service), as well as configure the resources for use.
- Navigate to the Storage Account resource, then Data storage - Containers
- Create a new Container, provide a Name,
keystone-uploads
, and set Public access level toBlob
, then click Create - Navigate to Security + networking - Access keys, copy the Storage account name and key1
- Navigate to the Web App (App Service) you created and go to Settings - Configuration
- Create three New application settings:
AZURE_STORAGE_ACCOUNT
as the Storage account name value you copied above.AZURE_STORAGE_KEY
as the key1 value you copied above.AZURE_STORAGE_CONTAINER
as the name of the container, which you specified askeystone-uploads
above.
- These will become the environment variables available to Keystone, click Save.
-
Navigate to the Azure Database for PostgreSQL server resource then Settings - Connection security
-
Set
Allow access to Azure services
toYes
and click Save -
Navigate to Overview and copy Server name and Server admin login name
-
Open the Azure Cloud Shell and log into the
psql
cli:psql --host <server> --user <username> --port=5432 --dbname postgres
-
Create a database for Keystone to use
CREATE DATABASE keystone;
then close the Cloud Shell- Optional - create a separate non server admin user (see this doc for guidance)
-
Navigate to the Web App (App Service) you created and go to Settings - Configuration
-
Create a New application setting named
DATABASE_URL
, which contains the connection string, encoded per Prisma's requirements (this will become the environment variables available to Keystone) and click Save, it will look something like:postgres://$username%40$serverName:$password@$serverName.postgres.database.azure.com:5432/$dbName
In this section, we'll use the Azure CLI to create the required resources. This will assume you have some familiarity with the Azure CLI and how to find the right values.
-
Create a new Resource Group
rgName=my-keystone-app location=westus az group create --name $rgName --location $location
-
Create a new Linux App Service Plan (ensure you change the
number-of-workers
andsku
to meet your scale requirements)appPlanName=keystone-app-service-plan az appservice plan create --resource-group $rgName --name $appPlanName --is-linux --number-of-workers 4 --sku S1 --location $location
-
Create a Web App (App Service) running Node.js 14
webAppName=my-keystone-app az webapp create --resource-group $rgName --name $webAppName --plan $appPlanName --runtime "NODE|14-lts"
-
Create a Storage Account
saName=mykeystoneapp az storage account create --resource-group $rgName --name $saName --location $location # Get the access key saKey=$(az storage account keys list --account-name $saName --query "[?keyName=='key1'].value" --output tsv) # Add a container to the storage account container=keystone-uploads az storage container create --name $container --public-access blob --access-key $saKey --account-name $saName
-
Create a Postgres database
serverName=my-keystone-db dbName=keystone username=keystone password=... # Create the server az postgres server create --resource-group $rgName --name $serverName --location $location --admin-user $username --admin-password $password --version 11 --sku-name B_Gen5_1 # Create the database az postgres db create --resource-group $rgName --name $dbName --server-name $serverName # Allow Azure resources through the firewall az postgres server firewall-rule create --resource-group $rgName --server-name $serverName --name AllowAllAzureIps --start-ip-address 0.0.0.0 --end-ip-address 0.0.0.0
-
Add configuration values to the Web App (App Service)
az webapp config appsettings set --resource-group $rgName --name $webAppName --setting AZURE_STORAGE_ACCOUNT=$saName az webapp config appsettings set --resource-group $rgName --name $webAppName --setting AZURE_STORAGE_KEY=$saKey az webapp config appsettings set --resource-group $rgName --name $webAppName --setting AZURE_STORAGE_CONTAINER=$container az webapp config appsettings set --resource-group $rgName --name $webAppName --setting DATABASE_URL=postgres://$username%40$serverName:$password@$serverName.postgres.database.azure.com:5432/$dbName
A Resource Manager template has been created that will provision all the resources that are required, as well as setup the appropriate settings across the AppService. Click the button below and fill out the parameters as required to create the resources. Alternatively, you can create a custom template deployment via the portal and upload the template file, or run the template form the Azure CLI.
The template is located at .azure/azuredeploy.json
As AppService is a PaaS hosting model, it is not possible to store files on disk, as it is not persisted between deployments nor is it shared between scale-out machines. The solution for this in Keystone is to use a custom field type https://github.com/keystonejs-contrib/k6-contrib/tree/main/packages/fields-azure.
For local development, you can either use the standard Keystone file/image field types (which stored on the local disk), or the Azurite emulator.
Azure AppService can be deployed to using CI/CD pipelines or via FTPS, refer to the Azure docs on how to do this for your preferred manner.
To start the Node.js application, AppService will run the npm start
command. As there is no guarantee that the symlinks created by npm install
were preserved (in the case of an upload from a CI/CD pipeline) it is recommended that the npm start
command directly references the Keystone entry point:
"scripts": {
"start": "node ./node_modules/@keystone-6/core/bin/cli.js start"
}
Keystone provides built in authentication but this can be replaced with a custom provider that supports Azure AD B2C. For this, you'll need the https://github.com/OpenSaasAU/keystone-nextjs-auth auth package and then follow the NextAuth docs on setting up Azure AD B2C.
Once Azure is configured, update your auth provider with nextjs-auth to use AzureADB2C like so:
const auth = createAuth({
listKey: "User",
identityField: "subjectId",
sessionData: `id name email`,
autoCreate: true,
userMap: { subjectId: "id", name: "name" },
accountMap: {},
profileMap: { email: "mail" },
providers: [
Providers.AzureADB2C({
clientId: process.env.AZURE_CLIENT_ID,
clientSecret: process.env.AZURE_CLIENT_SECRET,
scope: "offline_access User.Read",
tenantId: process.env.AZURE_TENANT_ID,
}),
],
});
To run the sample locally, you will need to use a Postgres database (either locally hosted, or hosted on Azure) and provide an Azure storage account (either an emulator like Azurite or an Azure resource).
This connection information will need to be made available as environment variables:
AZURE_STORAGE_ACCOUNT=...
AZURE_STORAGE_KEY=...
AZURE_STORAGE_CONTAINER=...
DATABASE_URL=...
AZURE_STORAGE_ACCOUNT_HOST=http://127.0.0.1:10000/