This was written on 8 November, 2019. Some of the issues mentioned below were discovered and resolved with the help of Microsoft support staff, and will hopefully be fixed in the near future.
The steps below are mostly taken from this tutorial, Deploying your Rails + PostgreSQL app on Microsoft Azure, but have also been influenced by these two other tutorials: Create a Ruby on Rails App in App Service on Linux and Build a Ruby and Postgres app in Azure App Service on Linux.
The descriptions of each step are my own personal understanding. I'm not an Azure expert, so they may not be correct 100% of the time.
Rails Version Note: The versions used are important since Azure doesn't support the latest versions. If your Rails App contains a package.json file, you will need to delete it in order to be able to deploy to Azure. This is because Azure uses the presence of that package.json file to flag your app as a node.js website, and not a Ruby website, and generates the incorrect deployment script.
Another Rails Version Note: Even if you've deleted the package.json file mentioned above, and the app still doesn't run, it's probably because of the Rails version used. I'm not sure where the boundary is, but I tried using version 5.2.3 and got errors, but using 5.1.6 worked.
Bundler Version Note: Azure also does not suport the latest version of Bundler. If you are running Bundler 2, you will need to downgrade bundler version for your app - version 1.17.2 works. You can check the version of Bundler used at the bottom of your Gemfile.lock file. Worst case scenario is you can just edit the version in there to get the deploy to work, although that's probably not advised since you shouldn't manually edit that file.
rails new example-app --database=postgresql
cd example-app
bundle
git add .
git commit -m "Initial commit"
This creates a brand new app called example-app with the database set to Postgres. Afterwards the bundle
command installs all of the required gems.
rails generate scaffold User name:string email:string
rails db:create
rails db:migrate
git add .
git commit -m "Implement User model, views and controllers"
This generates the User model, controller, and views. It then creates the databases locally so we can run our app through localhost.
rails s
Start the app using the above command and, in your browser, go to http://localhost:3000/users. The page should load properly and give you the option to add a new user.
You can download the CLI here: https://docs.microsoft.com/en-us/cli/azure/install-azure-cli?view=azure-cli-latest
You must then log in with the CLI using az login
. This should open your browser and prompt you to login. Once you're done, your terminal should have a JSON response in it, but you can pretty much ignore that.
Users must be authorized with Azure to be able to make deployments. Using the CLI, enter the following command az webapp deployment user set --user-name <username> --password <password>
. Note: usernames cannot contain the @ character.
All resources in Azure must be contained in a resource group, which helps keeps things organised. You can create one by entering az group create --location westeurope --name exampleResourceGroup
. I couldn't find a command to view a full list of available locations, but you can paste the command with an invalid string (eg. 'sadhjfajksdg'), and the error message response will list the valid ones for you.
An App Service Plan consists of the underlying virtual machines that will host the Azure App Services. The App Service Plan defines the region of the physical server where your app will be hosted on and the amount of storage, RAM, and CPU the physical servers will have. There are several different tiers, for different usage requirements.
You can create an App Service Plan using the following command: az appservice plan create --name exampleAppServicePlan --resource-group exampleResourceGroup --is-linux
. This creates a service plan inside of the resource group we just created, specified by the --resource-group
flag. It also specifies that we want the service plan to run on a linux server using the --is-linux
flag.
More information about this can be found here: https://docs.microsoft.com/en-us/cli/azure/appservice/plan?view=azure-cli-latest.
The Webapp is the actual webapp that will run on the infrastructure defined by the App Service Plan. You can create one with az webapp create --resource-group exampleResourceGroup --plan exampleAppServicePlan --name <unique_name> --runtime "RUBY|2.6.2" --deployment-local-git
. This will create the Webapp in the specified resource group, for the specified App Service Plan. The --runtime
flag tells the Webapp what kind of app we're going to deploy. You can view the available runtimes with az webapp list-runtimes --linux
. The --deployment-local-git
flag just tells that app that we'll deploy code to it from a local git repository.
More information about this can be found here: https://docs.microsoft.com/en-us/cli/azure/webapp?view=azure-cli-latest#az-webapp-create.
Once the command has run successfully, you should get a JSON response. Near the top of the response, look for the "deploymentLocalGitUrl" key. This is the url we will use to deploy to our app. It should look like this: https://<deployment_username>@<unique_name>.scm.azurewebsites.net/<unique_name>.git
It's probably also a good idea to enable logging for your app at this point: az webapp log config --name <unique_name> --resource-group exampleResourceGroup --web-server-logging filesystem
Before we deploy to Azure, we'll need to set up a git remote that we can push to. You can do this with git remote add azure <deployment_url>
. Once that's done, deploying to Azure is done using git push azure master
. It should prompt you for your password that you entered when setting up the deployment user in Step 5, so enter that. That should work as expected, but if you run into any trouble, try restarting the web app with az webapp restart --name <unique_name> --resource-group exampleResourceGroup
This is where you might encounter errors due to the versions mentioned in the Overview. Make sure you see remote: Generating deployment script for Ruby Web Site
when you deploy. If your app contains a package.json file, you might see remote: Generating deployment script for node.js Web Site
, which will cause the deployment to fail. If the correct deployment script is generated, but the deployment fails with messages remote: bundler failed; remote: To install the missing version, run 'gem install bundler:2.0.2'
, this means your version of Bundler used in your app is not supported by Azure. I don't know specifically which versions are or aren't supported, but 1.17.2 works.
Next we'll create the database that our app will connect to: az postgres server create --name <unique_db_name> --resource-group exampleResourceGroup --location "North Europe" --admin-user <db_username> --admin-password <db_password> --sku-name B_Gen5_1
. The --admin-user
and --admin-password
don't need to be the same as the deployment user, but may as well be if there's only one developer working with Azure. The --sku-name
field determines the size of the database, with B_Gen5_1 being the smallest.
More information about the command and optional flags can be found here: https://docs.microsoft.com/en-us/cli/azure/postgres/server?view=azure-cli-latest#az-postgres-server-create.
To access the database, we need to configure the firewall to allow certain IP addresses in. For now, we can just allow everything, but an actual production database would need to be carefully configured - I don't know enough about the subject to know what a proper configuration would look like. az postgres server firewall-rule create --name allIP --server <unique_db_name> --resource-group exampleResourceGroup --start-ip-address 0.0.0.0 --end-ip-address 255.255.255.255
Check that the firewall rule is configured correctly by trying to connect to the database from your terminal: psql -U <db_username>@<unique_db_name> -h <unique_db_name>.postgres.database.azure.com -p 5432 -d postgres
. If if lets you in, good, you can quit with \q
.
If you get this error: dyld: Library not loaded: /usr/local/opt/readline/lib/libreadline.7.dylib, just do this:
cd /usr/local/opt/readline/lib
ln -s libreadline.8.0.dylib libreadline.7.dylib
I don't really know what it does, but it was taken from here https://stackoverflow.com/questions/54261455/library-not-loaded-usr-local-opt-readline-lib-libreadline-7-dylib and it fixed the problem for me.
The default Rails app is not configured for a production database. To do this, open <app_root>/config/database.yml. By default, the production settings (at the bottom of the file) should look something like this:
production:
<<: *default
database: example-app_production
username: example-app
password: <%= ENV['EXAMPLE-APP_DATABASE_PASSWORD'] %>
Replace the settings so they look like this:
production:
<<: *default
host: <%= ENV['AZURE_DB_HOST'] %>
port: 5432
database: <%= ENV['AZURE_DB_DATABASE'] %>
username: <%= ENV['AZURE_DB_USERNAME'] %>
password: <%= ENV['AZURE_DB_PASSWORD'] %>
Commit the changes once finished. Don't worry about pushing to Azure, we'll do that later.
Now we need to create the environment variables referenced in the production database settings created above. These will need to be configured locally on your computer, and on the Azure webapp server. To start with the Azure server: az webapp config appsettings set --name <unique_name> --resource-group exampleResourceGroup --settings AZURE_DB_HOST="<unique_db_name>.postgres.database.azure.com" AZURE_DB_DATABASE="postgres" AZURE_DB_USERNAME="<db_username>@<unique_db_name>" AZURE_DB_PASSWORD="<db_password>"
To set them locally, use nano ~/.bash_profile
and add:
export AZURE_DB_HOST="<unique_db_name>.postgres.database.azure.com"
export AZURE_DB_DATABASE="postgres"
export AZURE_DB_USERNAME="<db_username>@<unique_db_name>"
export AZURE_DB_PASSWORD="<db_password>"
Once done, hit ctrl-x
to quit, and y
and then enter
to save. Your terminal may need to be restarted for these new variables to be recognised. Alternatively, you could just enter the same commands into your terminal to set the environment variables temporarily.
Now that we're connected to the database, we need to migrate it to reflect the schema of our app: rails db:migrate RAILS_ENV=production
.
Generate a rails secret using rails secret
and set it as an evironment variable: az webapp config appsettings set --name <unique_name> --resource-group exampleResourceGroup --settings RAILS_MASTER_KEY="<rails_secret>" SECRET_KEY_BASE="<rails_secret>" RAILS_SERVE_STATIC_FILES="true" ASSETS_PRECOMPILE="true"
Now that everything is set up, we just need to quickly precompile our assets manually: rake assets:precompile
. Commit that, and then push the application to azure: git push azure master
.
Any migrations or changes to the database in the future must be run manually using rails db:migrate RAILS_ENV=production
. They will not be run automatically on deploy like they are on AWS.
For if you're repeating these steps during testing, like I am.
Paste this into whatever text editor you use, then just replace all the placeholder names at once to speed things up
rails _5.1.6_ new <app_name> --database=postgresql
cd <app_name>
bundle
git add .
# Delete package.json and change Gemfile.lock BUNDLED WITH version
git commit -m "Initial commit"
rails generate scaffold User name:string email:string
rails db:create
rails db:migrate
git add .
git commit -m "Implement User model, views and controllers"
az group create --location westeurope --name <resourceGroupName>
az appservice plan create --name <appServicePlanName> --resource-group <resourceGroupName> --is-linux
az webapp create --resource-group <resourceGroupName> --plan <appServicePlanName> --name <webAppName> --runtime "RUBY|2.6.2" --deployment-local-git
az webapp log config --name <webAppName> --resource-group <resourceGroupName> --web-server-logging filesystem
git remote add azure https://<deploymentUserName>@<webAppName>.scm.azurewebsites.net/<webAppName>.git
git push azure master
az postgres server create --name <databaseName> --resource-group <resourceGroupName> --location "North Europe" --admin-user <databaseUsername> --admin-password <databasePassword> --sku-name B_Gen5_1
az postgres server firewall-rule create --name allIP --server <databaseName> --resource-group <resourceGroupName> --start-ip-address 0.0.0.0 --end-ip-address 255.255.255.255
# In database.yml
production:
<<: *default
host: <%= ENV['AZURE_DB_HOST'] %>
port: 5432
database: <%= ENV['AZURE_DB_DATABASE'] %>
username: <%= ENV['AZURE_DB_USERNAME'] %>
password: <%= ENV['AZURE_DB_PASSWORD'] %>
az webapp config appsettings set --name <webAppName> --resource-group <resourceGroupName> --settings AZURE_DB_HOST="<databaseName>.postgres.database.azure.com" AZURE_DB_DATABASE="postgres" AZURE_DB_USERNAME="<databaseUsername>@<databaseName>" AZURE_DB_PASSWORD="<databasePassword>"
export AZURE_DB_HOST="<databaseName>.postgres.database.azure.com"
export AZURE_DB_DATABASE="postgres"
export AZURE_DB_USERNAME="<databaseUsername>@<databaseName>"
export AZURE_DB_PASSWORD="<databasePassword>"
rails db:migrate RAILS_ENV=production
rails secret
az webapp config appsettings set --name <webAppName> --resource-group <resourceGroupName> --settings RAILS_MASTER_KEY="<rails_secret>" SECRET_KEY_BASE="<rails_secret>" RAILS_SERVE_STATIC_FILES="true" ASSETS_PRECOMPILE="true"
rake assets:precompile
git add .
git commit -m "Database settings and precompiled assets"
git push azure master
I have the same problem. Any idea how to fix it?