Craft CMS 3 Local Development with Lando Docker

Let’s install Craft CMS 3 on macOS by using Lan­do to set­up our local devel­op­ment envi­ron­ment. You can also fol­low these steps for Win­dows 10 Pro.

So what’s Lando? 

Lan­do is a tool that pro­vides a lay­er of abstrac­tion on top of Dock­er. So you can start lever­ag­ing Dock­er now and post­pone learn­ing about Dock­er­file and docker-compose to net­work con­tain­ers and set­ting up vol­umes and… 🤯

Lan­do comes from the Dru­pal com­mu­ni­ty but you can use it to work local­ly with Lar­avel, Word­Press, Djan­go, Rails or even lega­cy PHP projects. In addi­tion to run­ning a wide range of web projects, it’s also active­ly main­tained & has real­ly good doc­u­men­ta­tion.

Lando’s ben­e­fits:

  • One click installer 
  • Cross-plat­form (macOS, Win­dows and Linux) 
  • Recipes with sen­si­ble defaults 
  • Per project con­fig file .lando.yml
    which you com­mit to your Git repo to share your project’s tech stack! 
  • easy to enable SSL / HTTPS
  • uses faster & more light­weight Dock­er con­tain­ers ‑vs- resource heavy vir­tu­al machines

Alright. Enough back­sto­ry. Let’s get started.

  1. Go to Lan­do releas­es page & down­load most recent sta­ble installer for your OS. Look for the green Lat­est release tag. 

  2. Run the Lan­do installer.
    💥 IMPOR­TANT
    If you already have Dock­er Desk­top installed back­up any data­bas­es you might need because Dock­er Desk­top is pack­aged with Lan­do and it will over­write your cur­rent installation.

  3. Adjust Dock­er Desk­top settings. 
    💪 Increase resources for Dock­er
    After Lan­do has fin­ished installing. Open the Dock­er Desk­top pref­er­ences, go to Resources and increase CPUs & Mem­o­ry. I have a quad-core lap­top with 16GB RAM. I allo­cat­ed 4 CPUs, 6GB RAM, 4GB Swap and ~100GB to Disk Image.

Docker Resource Settings

⚠️ Dis­able Auto Update
Lan­do ships with its own ver­sion of Dock­er Desk­top so if you update it past the ver­sion Lan­do expects you’ll run into issues. 

In Pref­er­ences> Gen­er­al, uncheck Auto­mat­i­cal­ly check for updates.

  1. Install Craft CMS 3 via Lan­do CLI.
    We’re going to use the Lan­do LAMP recipe. So Lan­do will con­fig­ure Apache as our web serv­er, PHP, MySQL and Composer.

a. Cre­ate a project direc­to­ry mkdir craft3blog
b. cd into the new fold­er cd craft3blog
c. Now we’ll run lando init
This will start a wiz­ard where we’ll spec­i­fy our project’s tech stack. 

Here’s the out­put of what I selected:

alexagui@alexMBP ~/Code/craft/craft3blog $ lando init
? From where should we get your app's codebase? current working directory
? What recipe do you want to use? lamp
? Where is your webroot relative to the init destination? web
? What do you want to call this app? craft3blog

NOW WE'RE COOKING WITH FIRE!!!
Your app has been initialized!

Go to the directory where your app was initialized and run `lando start` to get rolling.
Check the LOCATION printed below if you are unsure where to go.

Oh... and here are some vitals:

 NAME      craft3blog
 LOCATION  /Users/alexagui/Code/craft/craft3blog
 RECIPE    lamp
 DOCS      https://docs.devwithlando.io/tutorials/lamp.html

Before start­ing Lan­do let’s check­out the .lando.yml file 

name: craft3blog
recipe: lamp
config:
  webroot: web

The recipe line packs in a lot of pow­er. It tells Lan­do spin up this project with most recent ver­sion of Apache, mysql & PHP. How­ev­er, let’s say our pro­duc­tion serv­er uses PHP 7.2 & Mari­aDB 10.2

Then we can update it like so: 

name: craft3blog
recipe: lamp
config:
  webroot: web
  php: '7.2'
  database: mariadb:10.2

In fact, I updat­ed mine to match the above.

d. Start Lan­do with lando start. You’ll see out­put sim­i­lar to this:

alexagui@alexMBP ~/Code/craft/craft3blog $ lando start
Let's get this party started! Starting app..
landoproxyhyperion5000gandalfedition_proxy_1 is up-to-date
Creating network "craft3blog_default" with the default driver
Creating volume "craft3blog_data_appserver" with default driver
Creating volume "craft3blog_home_appserver" with default driver
Creating volume "craft3blog_data_database" with default driver
Creating volume "craft3blog_home_database" with default driver
Creating craft3blog_appserver_1 ... done
Creating craft3blog_database_1  ... done
Waiting until appserver service is ready...
Waiting until database service is ready...
Waiting until database service is ready...
Waiting until database service is ready...
Waiting until database service is ready...
Waiting until database service is ready...
Waiting until database service is ready...

BOOMSHAKALAKA!!!

Your app has started up correctly.
Here are some vitals:

 NAME            craft3blog
 LOCATION        /Users/alexagui/Code/craft/craft3blog
 SERVICES        appserver, database
 APPSERVER URLS  https://localhost:32814
                 http://localhost:32815
                 http://craft3blog.lndo.site
                 https://craft3blog.lndo.site

Now let’s install Craft 3 by using the ver­sion of Com­pos­er installed by Lando. 
lando composer create-project craftcms/craft boop

YES. I’m installing into a boop fold­er. You’ll see why shortly. 

After sev­er­al screens of text you’ll see the boop fold­er with­in our cur­rent folder. 

alexagui@alexMBP ~/Code/craft/craft3blog $ ls
boop

But we actu­al­ly need the con­tents of that boop fold­er with­in our cur­rent folder. 

So let’s move all those files up one level. 
mv boop/* .
We also need to move some hid­den dot . files.
mv boop/.* .

And with that done we can remove the boop directory.
rm -rf boop

Q: Why didn’t I have Com­pos­er cre­ate the project with­in the cur­rent work­ing direc­to­ry and avoid the whole boop fold­er shuffle? 🤔 

A: Because Com­pos­er CAN­NOT install a project inside a direc­to­ry that already con­tains files. In our case, .lando.yml.

Before run­ning the Craft installer we need to con­fig­ure the the .env file. If you open it, you’ll see some­thing sim­i­lar to this:

# The environment Craft is currently running in ('dev', 'staging', 'production', etc.)
ENVIRONMENT="dev"

# The secure key Craft will use for hashing and encrypting data
SECURITY_KEY=""

# The database driver that will be used ('mysql' or 'pgsql')
DB_DRIVER="mysql"

# The database server name or IP address (usually this is 'localhost' or '127.0.0.1')
DB_SERVER="localhost"

# The database username to connect with
DB_USER="root"

# The database password to connect with
DB_PASSWORD=""

# The name of the database to select
DB_DATABASE=""

# The database schema that will be used (PostgreSQL only)
DB_SCHEMA="public"

# The prefix that should be added to generated table names (only necessary if multiple things are sharing the same database)
DB_TABLE_PREFIX=""

# The port to connect to the database with. Will default to 5432 for PostgreSQL and 3306 for MySQL.
DB_PORT=""

We need to fill in the data­base cre­den­tials. And we can find them via the lando info command.

alexagui@alexMBP ~/Code/craft/craft3blog $ lando info
[
  {
    service: 'appserver',
    urls: [
      'https://localhost:32814',
      'http://localhost:32815',
      'http://craft3blog.lndo.site',
      'https://craft3blog.lndo.site'
    ],
    type: 'php',
    via: 'apache',
    webroot: 'web',
    config: {},
    version: '7.2',
    meUser: 'www-data',
    hostnames: [
      'appserver.craft3blog.internal'
    ]
  },
  {
    service: 'database',
    urls: [],
    type: 'mariadb',
    internal_connection: {
      host: 'database',
      port: '3306'
    },
    external_connection: {
      host: 'localhost',
      port: '32769'
    },
    creds: {
      database: 'lamp',
      password: 'lamp',
      user: 'lamp'
    },
    config: {},
    version: '10.2',
    meUser: 'www-data',
    hostnames: [
      'database.craft3blog.internal'
    ]
  }
]

I want to point out 2 impor­tant keys:

internal_connection> host
Notice the val­ue is data­base. So in the Craft .env file change it from local­host to DB_SERVER="database”.

And if you look a bit fur­ther down you’ll see the creds key. Those are the val­ues that you need for data­base, user­name and pass­word. Lando’s LAMP recipe by default assigns lamp for those credentials. 

How­ev­er, I like to over­ride these defaults with my own data­base cre­den­tials which makes it eas­i­er to man­age mul­ti­ple projects. If all my Lan­do data­bas­es are called lamp it’ll be hard to keep track to which project a DB export named lamp_2019-10-07_11-10 AM.sql.gz belongs to. It also makes it eas­i­er to con­fig­ure my data­base client Sequel Pro for each project.

Open .lando.yml and add the services sec­tion like this:

name: craft3blog
recipe: lamp
config:
  webroot: web
  php: '7.2'
  database: mariadb:10.2
services:
  database:
    type: mariadb:10.2
    portforward: 3311
    creds:
      user: homestead
      password: secret
      database: craft3blog

Note that in addi­tion to pro­vid­ing my own cre­den­tials, I’m also spec­i­fy­ing an exter­nal port of 3311. The rea­son for hard­cod­ing an exter­nal port is because by default Lan­do will assign one ran­dom­ly. Which means if I want to con­nect local­ly using Sequel Pro I have to first run lando info to see which exter­nal port Lan­do assigned and update my Sequel Pro con­nec­tion. I keep track of Lan­do project ports in a Notion doc.

Save .lando.yml and run lando destroy.

alexagui@alexMBP ~/Code/craft/craft3blog $ lando destroy
? Are you sure you want to DESTROY? Yes
Preparing to resign craft3blog to the dustbin of history...
Stopping craft3blog_appserver ... done
Stopping craft3blog_appserver_1       ... done
Stopping craft3blog_database_1        ... done
Removing craft3blog_appserver ... done
Removing craft3blog_appserver_1       ... done
Removing craft3blog_database_1        ... done
Removing network craft3blog_default
Removing volume craft3blog_data_appserver
Removing volume craft3blog_home_appserver
Removing volume craft3blog_data_database
Removing volume craft3blog_home_database
Your app has paid the IRON PRICE. App destroyed!

I ran lando destroy because I’ve noticed that Lan­do con­tain­ers some­times are a bit finicky and will pre­serve con­fig set­tings. By destroy­ing it com­plete­ly I’m ensur­ing our data­base over­rides will take.

Start up Lan­do again.
lando start

And let’s con­firm our data­base cre­den­tials have been overridden.

alexagui@alexMBP ~/Code/craft/craft3blog $ lando info
[
  {
    service: 'appserver',
    urls: [
      'https://localhost:32814',
      'http://localhost:32815',
      'http://craft3blog.lndo.site',
      'https://craft3blog.lndo.site'
    ],
    type: 'php',
    via: 'apache',
    webroot: 'web',
    config: {},
    version: '7.2',
    meUser: 'www-data',
    hostnames: [
      'appserver.craft3blog.internal'
    ]
  },
  {
    service: 'database',
    urls: [],
    type: 'mariadb',
    internal_connection: {
      host: 'database',
      port: '3306'
    },
    external_connection: {
      host: 'localhost',
      port: '3311'
    },
    creds: {
      database: 'craft3blog',
      password: 'secret',
      user: 'homestead'
    },
    config: {},
    version: '10.2',
    meUser: 'www-data',
    hostnames: [
      'database.craft3blog.internal'
    ]
  }
]

I see our cus­tom data­base cre­den­tials & exter­nal port. Look­ing good. Time to update the Craft .env

# The environment Craft is currently running in ('dev', 'staging', 'production', etc.)
ENVIRONMENT="dev"

# The secure key Craft will use for hashing and encrypting data
SECURITY_KEY="HLXcXNT3C98Ss2qg"

# The database driver that will be used ('mysql' or 'pgsql')
DB_DRIVER="mysql"

# The database server name or IP address (usually this is 'localhost' or '127.0.0.1')
DB_SERVER="database"

# The database username to connect with
DB_USER="homestead"

# The database password to connect with
DB_PASSWORD="secret"

# The name of the database to select
DB_DATABASE="craft3blog"

# The database schema that will be used (PostgreSQL only)
DB_SCHEMA="public"

# The prefix that should be added to generated table names (only necessary if multiple things are sharing the same database)
DB_TABLE_PREFIX=""

# The port to connect to the database with. Will default to 5432 for PostgreSQL and 3306 for MySQL.
DB_PORT="3306"

In addi­tion to the cre­den­tials, I also entered the default MySQL data­base port of 3306. Don’t con­fuse this with the cus­tom exter­nal port we defined.

:sparkles: Craft 3.4 —> DB_DSN
Craft 3.4 dep­re­cat­ed the DB_DRIVER, DB_SERVER, DB_DATABASE, and DB_PORT keys. They replaced them with DB_DSN. So instead of the above sep­a­rate keys you’d have 1 line like this: 

DB_DSN="mysql:host=database;port=3306;dbname=craft3blog"

You also need to fill in the SECURITY_KEY. You can gen­er­ate it using a pass­word gen­er­a­tor like this one.

We’re final­ly ready to install Craft. We can do so by vis­it­ing the fol­low­ing URL 

http://craft3blog.lndo.site/admin/install

If all has gone well you should see a screen like this:

Craft CMS install screen

  1. Click the Install Craft button. 
  2. Accept the terms.
  3. Cre­ate your admin account.
    Enter your admin user, email & password. Create your account
  4. Set­up your site info.
    Enter the site name, base URL & language.
    Setup your site
  5. Then wait a lit­tle bit as Craft fin­ish­es installing.

You’ll be redi­rect­ed to the admin con­trol pan­el Dashboard. Dashboard

Wel­come to Craft. 🎉

There isn’t any sam­ple data or much of a fron­tend. That’s all left for you to cus­tomize as you see fit. 😉

When you’re done work­ing with a Lan­do project just run:
lando stop

alexagui@alexMBP ~/Code/craft/craft3blog $ lando stop
This party's over :( Stopping app
Stopping craft3blog_appserver_1 ... done
Stopping craft3

And when you’re com­plete­ly done work­ing with Lan­do you can shut­down all apps with:
lando poweroff

alexagui@alexMBP ~/Code/craft/craft3blog $ lando poweroff
NO!! SHUT IT ALL DOWN! Spinning Lando containers down...
Bye bye landoproxyhyperion5000gandalfedition_proxy_1 ...  done
Lando containers have been spun down.

So hope­ful­ly you’ve seen how con­ve­nient, flex­i­ble & pow­er­ful it is to use Lan­do for local devel­op­ment. One minor gripe is not being able to use a cus­tom local URLs like eaglepeakweb.test You must use appname.lndo.site but that’s a small price to pay for all the oth­er benefits.

If you have a fair­ly com­plex serv­er con­fig then a vir­tu­al machine or true Dock­er set­up might be more appropriate. 

Some advanced Lan­do tech­niques I rec­om­mend you try are: 

In the next arti­cle, we’ll start build­ing a favorite board games library in Craft 3. 🎲

👍 👎 What do you think of Lan­do? Let me know @AlexAguilar18 on Twitter.

Alex Aguilar

Partner, Software Engineer