The Twelve-Factor App: Best Practices for Modern Web Applications

D6ccd6409910643d05ddaea3b2cd6f13?s=47 David Zuelke
February 20, 2015

The Twelve-Factor App: Best Practices for Modern Web Applications

Presentation at ConFoo 2015 in Montreal, QC, Canada.

D6ccd6409910643d05ddaea3b2cd6f13?s=128

David Zuelke

February 20, 2015
Tweet

Transcript

  1. 3.
  2. 11.

    4/12 MAIN FACTORS, CODE ACCORDINGLY I. Codebase
 From one codebase,

    perform many deploys to staging, prod, ... II. Dependencies
 Your application explicitly declares userland and platform deps. III. Configuration
 Read from $_ENV: API keys, database credentials, SMTP hosts, ... XI. Logging
 file_put_contents("php://stderr",  "Yay  log");
  3. 13.

    II. DEPENDENCIES Applications have explicitly declared dependencies. $  cat  composer.json

      {      "require":  {          "php":  ">=5.3.3",          "ext-­‐mcrypt":  "*",          "symfony/symfony":  "~2.4.6",          "twig/extensions":  "~1.0",          "symfony/monolog-­‐bundle":  "~2.4"      }   } $  cat  package.json   {      "dependencies":  {          "express":  "~4.9.x",          "cool-­‐ascii-­‐faces":  "~1.3.x"      },      "engines":  {          "node":  "0.10.x"      }   }
  4. 16.

    III. CONFIGURATION Store config in the environment. Assumption: same code

    but different configuration per deployment target
  5. 17.

    III. CONFIGURATION Store config in the environment. $transport  =  Swift_SmtpTransport::newInstance(


           getenv('EMAIL_HOST'),  getenv('EMAIL_PORT')?:25
 )
        -­‐>setUsername(getenv('EMAIL_USERNAME'))
        -­‐>setPassword(getenv('EMAIL_PASSWORD'))
 ; Assumption: same code but different configuration per deployment target $  heroku  config:set  EMAIL_HOST=smtp.blah.com  EMAIL_PORT=827  \   EMAIL_USERNAME=joecool  EMAIL_PASSWORD=itwasadarkandstormynight
  6. 19.

    III. CONFIGURATION Store config in the environment. $smtp  =  parse_url(getenv('SMTP_GATEWAY_URL'));

      $transport  =  Swift_SmtpTransport::newInstance(
        $smtp['host'],  $smtp['port']
 )
        -­‐>setUsername($smtp['user'])
        -­‐>setPassword($smtp['pass'])
 ; Assumption: same code but different configuration per deployment target $  heroku  config:set  SMTP_GATEWAY_URL=\   smtp://joecool:itwasadarkandstormynight@smtp.blah.com:827
  7. 22.

    $  heroku  create  dz-­‐php-­‐test   Creating  dz-­‐php-­‐test...  done,  stack  is

     cedar-­‐14   http://dz-­‐php-­‐test.herokuapp.com/  |  git@heroku.com:dz-­‐php-­‐….git   $  git  push  heroku  master   -­‐-­‐-­‐-­‐-­‐>  PHP  app  detected   -­‐-­‐-­‐-­‐-­‐>  Setting  up  runtime  environment...                -­‐  PHP  5.5.16                -­‐  Apache  2.4.10                -­‐  Nginx  1.6.0   -­‐-­‐-­‐-­‐-­‐>  Installing  PHP  extensions:                -­‐  opcache  (automatic;  bundled)                -­‐  memcached  (composer.json;  downloaded)                -­‐  intl  (composer.json;  bundled)   -­‐-­‐-­‐-­‐-­‐>  Installing  dependencies...                Composer  version  05d991  2014-­‐10-­‐28  12:36:19                Loading  composer  repositories  with  package  information                Installing  dependencies  from  lock  file                    -­‐  Installing  monolog/monolog  (1.9.1)                        Loading  from  cache                          Generating  optimized  autoload  files   -­‐-­‐-­‐-­‐-­‐>  Discovering  process  types                Procfile  declares  types  -­‐>  web   -­‐-­‐-­‐-­‐-­‐>  Compressing...  done,  57.4MB   -­‐-­‐-­‐-­‐-­‐>  Launching...  done,  v3                http://dz-­‐php-­‐test.herokuapp.com/  deployed  to  Heroku
  8. 25.

    V. BUILD, RELEASE, RUN A build step vendors dependencies, prepares

    assets, etc. A release step creates a package from build and config. A runtime step executes, without special knowledge.
  9. 26.

    V. BUILD, RELEASE, RUN A build step vendors dependencies, prepares

    assets, etc. A release step creates a package from build and config. A runtime step executes, without special knowledge.
  10. 29.

    X. DEV/PROD PARITY Keep dev, stage and prod envs as

    similar as possible. SQLite ≠ MySQL Apache ≠ Nginx File based sessions ≠ Redis based sessions
  11. 30.

    X. DEV/PROD PARITY Keep dev, stage and prod envs as

    similar as possible. SQLite ≠ MySQL Apache ≠ Nginx File based sessions ≠ Redis based sessions
  12. 31.

    X. DEV/PROD PARITY Keep dev, stage and prod envs as

    similar as possible. SQLite ≠ MySQL Apache ≠ Nginx File based sessions ≠ Redis based sessions If apt-­‐get or brew don't get the job done on your box: Vagrant is always your friend!
  13. 36.

    VI. PROCESSES heroku-­‐ruby-­‐app  $  cat  Procfile   worker:  env  TERM_CHILD=1

     bundle  exec  rake  resque:work   web:  bundle  exec  unicorn  -­‐p  $PORT  -­‐c  ./config/unicorn.rb
  14. 37.

    VI. PROCESSES heroku-­‐java-­‐app  $  cat  Procfile   worker:  sh  worker/target/bin/worker

      web:  java  $JAVA_OPTS  -­‐jar  target/dependency/jetty-­‐runner.jar  -­‐-­‐ port  $PORT  target/*.war
  15. 40.

    VI. PROCESSES heroku-­‐php-­‐app  $  cat  Procfile   worker:  php  background.php

      web:  vendor/bin/heroku-­‐php-­‐apache2  #  or  heroku-­‐php-­‐nginx automatically injected into the app during build
  16. 41.

    RUNNING PHP APPS LOCALLY heroku-­‐php  $  composer  require  -­‐-­‐dev  heroku/heroku-­‐buildpack-­‐php

      ./composer.json  has  been  updated   Loading  composer  repositories  with  package  information   Updating  dependencies  (including  require-­‐dev)      -­‐  Installing  heroku/heroku-­‐buildpack-­‐php  (v43)          Loading  from  cache   Writing  lock  file   Generating  autoload  files   heroku-­‐php  $  foreman  start   14:30:45  web.1  |  started  with  pid  11175   14:30:46  web.1  |  Booting  on  port  5000...   14:30:46  web.1  |  Starting  php-­‐fpm...   14:30:46  web.1  |  Starting  nginx...
  17. 45.

    VIII. CONCURRENCY $  heroku  ps:scale  web=10   Scaling  dynos...  done,

     now  running  web  at  10:1X.   $  heroku  ps   ===  web  (1X):  `vendor/bin/heroku-­‐php-­‐apache2  web/`   web.1:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.2:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.3:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.4:  starting  2014/11/05  20:36:38  (~  4s  ago)   web.5:  starting  2014/11/05  20:36:38  (~  4s  ago)   web.6:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.7:  starting  2014/11/05  20:36:38  (~  4s  ago)   web.8:  starting  2014/11/05  20:36:39  (~  4s  ago)   web.9:  starting  2014/11/05  20:36:38  (~  4s  ago)   web.10:  starting  2014/11/05  20:36:39  (~  4s  ago)   $  heroku  ps:scale  web=1   Scaling  dynos...  done,  now  running  web  at  1:1X.
  18. 48.

    XII. ADMIN PROCESSES Management tasks like DB migrations are one-off

    processes. They run in isolation, just like all other "dynos". With the same release: same code, same config!
  19. 49.

    XII. ADMIN PROCESSES Management tasks like DB migrations are one-off

    processes. $  heroku  run  "php  app/console  doctrine:migrations:migrate"
 Running  `php  app/console…`  attached  to  terminal...  up,  run.4062   Migrating  up  to  20100416130452  from  0      >>  migrating  20100416130452            -­‐>  CREATE  TABLE  users  (username  VARCHAR(255)  NOT  NULL,                  password  VARCHAR(255)  NOT  NULL)  ENGINE  =  InnoDB      >>  migrated They run in isolation, just like all other "dynos". With the same release: same code, same config!
  20. 52.

    $  heroku  addons:add  heroku-­‐postgresql   Adding  heroku-­‐postgresql  on  dz-­‐php-­‐test...  done,

     v6  (free)   Attached  as  HEROKU_POSTGRESQL_AQUA_URL   Use  `heroku  addons:docs  heroku-­‐postgresql`  to  view  documentati…   $  heroku  addons:add  heroku-­‐postgresql   Adding  heroku-­‐postgresql  on  dz-­‐php-­‐test...  done,  v7  (free)   Attached  as  HEROKU_POSTGRESQL_AMBER_URL   Use  `heroku  addons:docs  heroku-­‐postgresql`  to  view  documentati…   $  heroku  addons:add  redistogo   Adding  redistogo  on  dz-­‐php-­‐test...  done,  v8  (free)   Use  `heroku  addons:docs  redistogo`  to  view  documentation.   $  heroku  addons:add  newrelic   Adding  newrelic  on  dz-­‐php-­‐test...  done,  v9  (free)   Use  `heroku  addons:docs  newrelic`  to  view  documentation.   $  heroku  config   ===  dz-­‐php-­‐test  Config  Vars   DATABASE_URL:                                postgres://u:p@host12:5432/db18172   HEROKU_POSTGRESQL_AMBER_URL:  postgres://u:p@host42:5198/db24438   HEROKU_POSTGRESQL_AQUA_URL:    postgres://u:p@host12:5432/db18172   NEW_RELIC_LICENSE_KEY:              c9eecdcf9523862f981e   REDISTOGO_URL:                              redis://u:p@i8.redistogo.com:9092/
  21. 56.

    heroku-­‐php  $  git  rm  Procfile   heroku-­‐php  $  composer  require

     -­‐-­‐ignore-­‐platform-­‐reqs  hhvm:~3.5   ./composer.json  has  been  updated   Loading  composer  repositories  with  package  information   Updating  dependencies  (including  require-­‐dev)   Nothing  to  install  or  update   Generating  autoload  files   heroku-­‐php  $  git  add  composer.*   heroku-­‐php  $  git  ci  -­‐m  'use  HHVM'   heroku-­‐php  $  git  push  heroku  master   -­‐-­‐-­‐-­‐-­‐>  PHP  app  detected   -­‐-­‐-­‐-­‐-­‐>  Detected  request  for  HHVM  3.5.0  in  composer.json.   -­‐-­‐-­‐-­‐-­‐>  Setting  up  runtime  environment...                -­‐  HHVM  3.5.0                -­‐  Apache  2.4.10                -­‐  Nginx  1.6.0   -­‐-­‐-­‐-­‐-­‐>  Building  runtime  environment...                No  Procfile,  using  'web:  vendor/bin/heroku-­‐hhvm-­‐apache2'   -­‐-­‐-­‐-­‐-­‐>  Compressing...  done,  77.4MB   -­‐-­‐-­‐-­‐-­‐>  Launching...  done,  v3                http://dz-­‐php-­‐test.herokuapp.com/  deployed  to  Heroku
  22. 57.
  23. 58.

    THE TWELVE- FACTOR APP Thanks for listening! Contact: @dzuelke &

    dz@heroku.com. rate my talk on joind.in!