If you liked what you've learned so far, dive in!
Subscribe to get access to this tutorial plus
video, code and script downloads.
With a Subscription, click any sentence in the script to jump to that part of the video!
Login SubscribeOur site is at least functional. Well any page that doesn't use the database is functional... like the about page. But it is super ugly. Oof. Why? It's simple! Our CSS file - build/styles.css
is missing! Oooh, a mystery!
Our MooTube asset setup is pretty awesome: instead of having simple CSS files, we're getting sassy with Sass! And to build that into CSS, we're using an awesome library called Webpack Encore. Actually, we have a tutorial on Webpack... so go watch that if you're curious!
Basically, Webpack Encore is a Node executable: you run it from the command line, and it - in our simple app - transforms that Sass file into build/styles.css
.
The reason styles.css
wasn't deployed is that its directory - /web/build
- is ignored in .gitignore
!
... lines 1 - 20 | |
/web/build |
We need to do more work to deploy it.
But before we get too far into that, there's one other little command that you often run during deployment:
bin/console assets:install --symlink
Actually, when you run composer install
, this command is run automatically... so you may not even realize it's happening. And for deploy... well... you may or may not even need it! Here's the deal: sometimes, a bundle - usually a third-party bundle - will come with some CSS, JS or other public assets. Those files, of course, live in the vendor/
directory... which is a problem... because it means they're not publicly accessible. To make them public, we run this command. For each bundle that has public assets, it creates a symlink in the web/bundles/
directory.
For our app... yea... we don't have any bundles that do this! But, let's run that command on deploy to be safe. Open up after-symlink-shared.yml
. Let's add a new task called "Install bundle assets":
... lines 1 - 14 | |
- name: Warm up the cache | |
command: '{{ release_console_path }} cache:warmup --env=prod' | |
- name: Install bundle assets | |
... lines 19 - 45 |
Set this to run a command, and use the release_console_path
variable that we setup in deploy.yml
:
- hosts: aws | |
... lines 3 - 14 | |
vars: | |
release_console_path: "{{ ansistrano_release_path.stdout }}/bin/console" | |
... lines 17 - 39 |
Add assets:install --symlink
and then --env=prod
:
... lines 1 - 17 | |
- name: Install bundle assets | |
command: '{{ release_console_path }} assets:install {{ release_web_path }} --symlink --no-debug --env=prod' | |
... lines 20 - 45 |
Tip
The --symlink
is optional, and depending on your setup, you may need to not
pass this flag
That's important: we need to run all of our console commands with --env=prod
. Running commands in dev
mode won't even work, because our require-dev
Composer dependencies were never installed.
Perfect!
Let's move on to the real task: building our assets on production. I'm not going to talk about Encore too much, but building the assets is a two-step process.
First, run:
yarn install
to download all of your Node dependencies. Basically, this reads package.json
{ | |
"devDependencies": { | |
"@symfony/webpack-encore": "^0.12.0", | |
"node-sass": "^4.5.3", | |
"sass-loader": "^6.0.6" | |
} | |
} |
And downloads that stuff into a node_modules/
directory. It's basically like Composer for Node.
Step 2 is to run Encore and build your assets. First, I'll clear out the build directory:
rm -rf web/build/*
Then, run:
./node_modules/.bin/encore production
Cool! Check it out! Yes! We still have styles.css
, and it's beautifully minified.
So how can we do this during deploy? Well... we have a few options. First, you could decide to run these commands locally and commit those files to your repository. That makes deploying easy... but you need to remember to run this command before each deploy. And committing built files to your repo is a bummer. This is an easy, but hacky way to handle things.
But... we do need to run these commands somewhere. The most obvious solution is actually to run these commands on your production server during deployment. This is what we're going to do... but it's also a bummer. It means that we will need to install Node on our production server... just to build these assets.
So, if you really don't like the idea of running these commands on production, you have a few other options. If you already have a build system of some sort, you could build your assets on that machine, upload them to S3 or something similar, then download them during deployment. Or, skip the downloading part, and update your script and link tags to point to S3 or some CDN.
A second option is to add a play to your deploy playbook that would first build those assets locally, before using the copy
module to move them up to production.
We're going to build the assets on production. This is not the best solution, but it's easy, and works great in most situations. The only big requirement is that we need to install Node and Yarn there. I'm going to open up my provision playbook: playbook.yml
. Then, near the bottom, paste some tasks that do this:
- hosts: webserver | |
... lines 3 - 33 | |
tasks: | |
... lines 35 - 124 | |
# Node | |
- name: Register NodeJS distribution | |
shell: 'curl -sL https://deb.nodesource.com/setup_8.x | sudo -E bash -' | |
changed_when: false | |
- name: Install NodeJS | |
become: true | |
apt: | |
name: nodejs | |
state: latest | |
update_cache: yes | |
# Yarn | |
- name: Add Yarn APT key | |
become: true | |
apt_key: | |
url: 'https://dl.yarnpkg.com/debian/pubkey.gpg' | |
state: present | |
- name: Add Yarn to the source lists | |
become: true | |
lineinfile: | |
path: '/etc/apt/sources.list.d/yarn.list' | |
regexp: 'deb https://dl.yarnpkg.com/debian/ stable main' | |
line: 'deb https://dl.yarnpkg.com/debian/ stable main' | |
create: yes | |
- name: Install Yarn package manager | |
become: true | |
apt: | |
name: yarn | |
state: latest | |
update_cache: yes | |
... lines 158 - 171 |
If you're not using Ansible to provision your server, just install Node and yarn however you want. Let's get this running!
ansible-playbook ansible/playbook.yml -i ansible/hosts.ini --ask-vault-pass -l aws
Use beefpass
for the password. Go go go!
While that's doing its magic, open after-symlink-shared.yml
. We need to run 2 commands on deploy. Add a new task called "Install Node dependencies". Use command
set to yarn install
. Oh, and make sure this runs in our project directory: add args
then chdir
set to our handy ansistrano_release_path.stdout
:
... lines 1 - 17 | |
- name: Install bundle assets | |
command: '{{ release_console_path }} assets:install --symlink --env=prod' | |
- name: Install Node dependencies | |
command: yarn install | |
args: | |
chdir: '{{ ansistrano_release_path.stdout }}' | |
... lines 25 - 32 |
Copy that to make the second task: Install Webpack Encore assets
. For the command, use ./node_modules/.bin/encore production
:
... lines 1 - 20 | |
- name: Install Node dependencies | |
command: yarn install | |
args: | |
chdir: '{{ ansistrano_release_path.stdout }}' | |
- name: Install Webpack Encore assets | |
command: './node_modules/.bin/encore production' | |
args: | |
chdir: '{{ ansistrano_release_path.stdout }}' | |
... lines 30 - 37 |
Ok, that's pretty easy! The annoying part is just needing to setup Node on your production server. Let's go back and check on the provision!
Oh boy... let's fast forward... ding!
Thanks to that, we should have Node and yarn up on the server. Let's deploy! Same command, but with deploy.yml
, and we don't need the -l aws
... this already only runs against the aws
host:
ansible-playbook ansible/deploy.yml -i ansible/hosts.ini --ask-vault-pass
Use beefpass
, deploy master
and... hold your breath! Most of this will be the same... but watch those two new tasks. The Node dependencies will take some time to install at first.
Ok done! No errors. So... let's try it! Refresh! We have CSS! Yes!
We are finally ready to fix the database... and our poor homepage.
"Houston: no signs of life"
Start the conversation!
// composer.json
{
"require": {
"php": ">=5.5.9",
"doctrine/doctrine-bundle": "^1.6", // 1.6.8
"doctrine/orm": "^2.5", // v2.7.2
"incenteev/composer-parameter-handler": "^2.0", // v2.1.2
"sensio/distribution-bundle": "^5.0.19", // v5.0.20
"sensio/framework-extra-bundle": "^3.0.2", // v3.0.26
"symfony/monolog-bundle": "^3.1.0", // v3.1.0
"symfony/polyfill-apcu": "^1.0", // v1.4.0
"symfony/swiftmailer-bundle": "^2.3.10", // v2.6.3
"symfony/symfony": "3.3.*", // v3.3.5
"twig/twig": "^1.0||^2.0", // v1.34.4
"doctrine/doctrine-migrations-bundle": "^1.2", // v1.2.1
"predis/predis": "^1.1", // v1.1.1
"composer/package-versions-deprecated": "^1.11" // 1.11.99
},
"require-dev": {
"sensio/generator-bundle": "^3.0", // v3.1.6
"symfony/phpunit-bridge": "^3.0", // v3.3.5
"doctrine/data-fixtures": "^1.1", // 1.3.3
"hautelook/alice-bundle": "^1.3" // v1.4.1
}
}
# ansible/requirements.yml
-
src: DavidWittman.redis
version: 1.2.4
-
src: ansistrano.deploy
version: 2.7.0
-
src: ansistrano.rollback
version: 2.0.1