Craft 3 on Heroku: creating a website using CraftCMS, Heroku, AWS and CloudFlare
Posted 01 Jan 2019
This guide will help you setup a site with the following:
- A CMS built using Craft 3
- Hosted on Heroku using Apache and PHP 7.1
- File serving using S3 and CloudFront CDN delivery
- Build process using Grunt, with support for SASS and Node
This guide assumes the use of AWS, GitHub and Heroku. The guide will get you up and running both locally and on Heroku.
Setting up Craft 3
Craft 3 is managed as a Composer package, which makes it easier to maintain and update than Craft 2.
Here’s a summary of what’s provided on Craft’s install guide:
# Install composer
brew install composer
# Create Craft 3 project
composer create-project -s RC craftcms/craft PROJECT_DIR
Update your .env
and set the environment to local. Open config/general.php
and add a new setting - replacing SITE_URL
with your own, e.g. torbensko.local
:
// Local environment settings
'local' => [
// Base site URL
'siteUrl' => 'http://SITE_URL',
// Dev Mode (see https://craftcms.com/support/dev-mode)
'devMode' => true,
],
Local Apache
Although macOS ships with Apache, I find it easier to use a Brew version. This is in part because the configuration for the OS version resets with every major OS update. Also, the macOS version is often a minor version or two behind.
Much of the following is based on the guide found on the Grav website. The short version is:
# Stop the default Apache
sudo apachectl stop
sudo launchctl unload -w /System/Library/LaunchDaemons/org.apache.httpd.plist 2> /dev/null
# Install Apache 2.4
brew install httpd24 --with-privileged-ports --with-http2
sudo brew services start httpd
# Install PHP 7.1 with Postgres and Apache support
brew install php71 --with-httpd24 --with-postgresql
You then need to open /usr/local/etc/httpd/httpd.conf
and make the following modifications:
- Change the port:
8080
=>80
- Change root directory:
/usr/local/var/www
=>/Users/USERNAME/Sites
- Allow for .htaccess overrides:
Override None
=>Override All
-
Allow for
mod_rewrite
by uncommenting:LoadModule rewrite_module lib/httpd/modules/mod_rewrite.so
- Change the user to yourself
- Change group to your own (typically staff)
-
Enable PHP on
*.php
files by adding:<IfModule dir_module> DirectoryIndex index.php index.html </IfModule> <FilesMatch \.php$> SetHandler application/x-httpd-php </FilesMatch>
-
Uncomment the vhosts file. I like to tweak this line and move the configuration file to ~/Sites:
Include /Users/USERNAME/Sites/httpd-vhosts.conf
To finish off the setup you need to create a httpd-vhosts.conf
file. Add the following entry for your site, remembering to replace PROJECT_DIR
, USERNAME
and SITE_URL
(e.g. torbensko.local) with your own:
<VirtualHost *:80>
DocumentRoot "/Users/USERNAME/PROJECT_DIR/web"
ServerName SITE_URL
<Directory "/Users/USERNAME/PROJECT_DIR">
Options Indexes MultiViews FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
Require all granted
</Directory>
</VirtualHost>
To make your new site available at the specified URL, open /etc/hosts
and add - replacing SITE_URL
with same as above:
127.0.0.1 SITE_URL
Once done you’ll need to restart Apache:
sudo apachectl -k restart
Open your browser and navigate to http://SITE_URL/admin and follow the instructions.
Database
As Heroku has strong support for Postgres, it’s easier to use Craft with Postgres. To get up and running locally, download and install Postgres:
brew install postgres
brew services start postgresql
# Create a database:
createuser DB_NAME
createdb -O DB_USER -Eutf8 DB_NAME
By default, Craft like to use separate environment variables for the database name, host, etc. By comparison Heroku uses a single variable, DATABASE_URL
. It’s easier to use this variable so we can take advantage of some of Heroku’s database features, including credential cycling and database switching.
To allow Craft to accept the DATABASE_URL variable open config/db.php
and replace with:
<?php
// e.g. postgres://USERNAME:PASSWORD@SERVER:PORT/DB_NAME
preg_match('|postgres://([a-z0-9]*):([a-z0-9]*)@([^:]*):([0-9]*)/(.*)|i', getenv('DATABASE_URL'), $matches);
$user = $matches[1];
$password = $matches[2];
$server = $matches[3];
$port = $matches[4];
$database = $matches[5];
return [
'driver' => "pgsql",
'server' => $server,
'user' => $user,
'password' => $password,
'database' => $database,
'schema' => getenv('DB_SCHEMA'),
'tablePrefix' => getenv('DB_TABLE_PREFIX'),
'port' => $port
];
Open .env
and add:
DATABASE_URL="postgres://DB_USER:@localhost:5432/DB_NAME"
To inspect and modify your database, I recommend using Postico. You can install this using Homebrew Cask:
brew cask install postico
Grunt and SASS
To use Grunt to build the CSS, do the following:
npm init
npm i autoprefixer dotenv grunt grunt-cli grunt-concurrent grunt-contrib-clean grunt-contrib-copy grunt-contrib-watch grunt-postcss grunt-sass jit-grunt --save
Create a Gruntfile.js in your project folder and add:
'use strict';
require('dotenv').config();
module.exports = function(grunt) {
require('jit-grunt')(grunt, {});
grunt.initConfig({
watch: {
sass: {
files: ['scss/{,*/}*.{scss,sass}'],
tasks: ['build:css']
},
},
// Build the CSS
sass: {
options: {
outputStyle: process.env.OPTIMISE_CSS ? 'compressed' : 'nested',
includePaths: ['node_modules']
},
dist: {
files: {
'web/styles/main.css': 'scss/main.scss'
}
}
},
// Add vendor prefixed styles
postcss: {
options: {
processors: [require('autoprefixer')({ browsers: ['last 1 version', 'ie 9'] })]
},
dist: {
files: [
{
expand: true,
cwd: 'web/styles/',
src: '{,*/}*.css',
dest: 'web/styles/'
}
]
}
},
});
grunt.registerTask('build:css', [
'sass',
'postcss'
]);
grunt.registerTask('build', [
'build:css'
]);
};
Create a PROJECT_DIR/scss
folder and add a main.scss. Then run grunt build to do a one time build or run grunt watch to build automatically on changes.
If your CSS is small enough (say < 50kb), I would recommend embedding it into your HTML. Although this bloats your HTML it avoids an extra server request and generally improves your site performance.
To do so install the Inlin plugin:
composer require aelvan/inlin
Add to your page layout:
<style>{{ craft.inlin.er('styles/main.css', true) | raw }}</style>
Setting up S3 for assets
Install the S3 plugin either via the web interface or via composer:
composer require craftcms/aws-s3
Open Craft and enable the plugin via the control panel.
Assuming you know how to use AWS, log into your AWS account and create a new bucket. Then create a new programatic IAM user for the CMS. You’ll need to give the user permissions to access the bucket. To avoid giving blanket access, I use the following permission:
{
"Version": "2012-10-17",
"Statement": [
{
"Sid": "ListS3",
"Effect": "Allow",
"Action": [
"s3:ListAllMyBuckets",
"s3:ListBucket",
"s3:HeadBucket",
"s3:ListObjects"
],
"Resource": "*"
},
{
"Sid": "CmsBucketAccess",
"Effect": "Allow",
"Action": [
"s3:PutAnalyticsConfiguration",
"s3:GetObjectVersionTagging",
"s3:CreateBucket",
"s3:ReplicateObject",
"s3:GetObjectAcl",
"s3:DeleteBucketWebsite",
"s3:PutLifecycleConfiguration",
"s3:GetObjectVersionAcl",
"s3:PutObjectTagging",
"s3:DeleteObject",
"s3:GetIpConfiguration",
"s3:DeleteObjectTagging",
"s3:GetBucketWebsite",
"s3:PutReplicationConfiguration",
"s3:DeleteObjectVersionTagging",
"s3:GetBucketNotification",
"s3:PutBucketCORS",
"s3:GetReplicationConfiguration",
"s3:ListMultipartUploadParts",
"s3:PutObject",
"s3:GetObject",
"s3:PutBucketNotification",
"s3:PutBucketLogging",
"s3:GetAnalyticsConfiguration",
"s3:GetObjectVersionForReplication",
"s3:GetLifecycleConfiguration",
"s3:ListBucketByTags",
"s3:GetInventoryConfiguration",
"s3:GetBucketTagging",
"s3:PutAccelerateConfiguration",
"s3:DeleteObjectVersion",
"s3:GetBucketLogging",
"s3:ListBucketVersions",
"s3:ReplicateTags",
"s3:RestoreObject",
"s3:GetAccelerateConfiguration",
"s3:GetBucketPolicy",
"s3:GetObjectVersionTorrent",
"s3:AbortMultipartUpload",
"s3:PutBucketTagging",
"s3:GetBucketRequestPayment",
"s3:GetObjectTagging",
"s3:GetMetricsConfiguration",
"s3:DeleteBucket",
"s3:PutBucketVersioning",
"s3:ListBucketMultipartUploads",
"s3:PutMetricsConfiguration",
"s3:PutObjectVersionTagging",
"s3:GetBucketVersioning",
"s3:GetBucketAcl",
"s3:PutInventoryConfiguration",
"s3:PutIpConfiguration",
"s3:GetObjectTorrent",
"s3:PutBucketWebsite",
"s3:PutBucketRequestPayment",
"s3:GetBucketCORS",
"s3:GetBucketLocation",
"s3:ReplicateDelete",
"s3:GetObjectVersion"
],
"Resource": "arn:aws:s3:::YOUR_BUCKET_NAME/*"
}
]
}
Obviously use these permissions at your own risk- I don’t provide any guarantees on the quality or reliability of this permission. Please let me know if you have any recommendations for refining the permission.
Within the Craft control panel, create a new asset volume. Once you copy the IAM credentials in you should be able to select your bucket.
To further increase your performance, you can also use AWS’s CloudFront. Just setup up the distribution for you bucket and add the details to your asset volume via the Craft control panel.
Setting up Heroku
Assuming you know the basics of Heroku, log in and do the following:
- Create a new app
- Provision the Heroku Postgres add-on. The free tier should initially serve you well.
- (Optionally) attach the project to your GitHub repo and enable automatic deploys.
Locally install the Heroku CLI:
brew install heroku/brew/heroku
Log into the tool (heroku auth:login) and add the NodeJS buildpack to your app so the Grunt process will work.
heroku buildpacks:add --index 2 heroku/nodejs --app HEROKU_APP_NAME
Add to your project a Procfile with the following:
web: vendor/bin/heroku-php-apache2 web
Publishing
If you have setup automatic deploys you can deploy the code using: git push. To push a copy of your database to the remote server run the following- replacing HEROKU_APP_NAME with your Heroku app name:
# If the DB exists you'll need to reset it - please consider taking a backup first
heroku pg:reset DATABASE_URL --app HEROKU_APP_NAME
# Replace DB_NAME with your local database name and HEROKU_APP_NAME.
heroku pg:push DB_NAME DATABASE_URL --app HEROKU_APP_NAME
If you wish to take a copy of your remote database, you can use the following command:
# Replace DB_NAME and HEROKU_APP_NAME
heroku pg:pull DATABASE_URL DB_NAME-$(date +%F) --app HEROKU_APP_NAME
If you want to keep multiple versions of the database locally, you can instead use:
# You'll need to update DATABASE_URL in your `.env` file
heroku pg:pull DATABASE_URL DB_NAME-$(date +%F) --app HEROKU_APP_NAME
Concluding remarks
This should have you up and running with a Heroku/Craft 3 site. If you have any further questions about the process please leave me a comment below.