Save 37% off PRO during our Black Friday Sale! »

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. THE TWELVE-FACTOR APP Best Practices for Modern Web Applications

  2. David Zuelke

  3. None
  4. dz@heroku.com

  5. @dzuelke

  6. “The Twelve-Factor App”

  7. “The Twelve-Factor App”

  8. “The Twelve-Factor App” is a manifesto, a methodology, a condensed

    collection of experiences.
  9. Its goals are scalability, maintainability, portability.

  10. 12factor.net

  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");
  12. II. DEPENDENCIES Applications have explicitly declared dependencies.

  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"      }   }
  14. do not check your vendors/ directory into Git

  15. III. CONFIGURATION Store config in the environment.

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

    but different configuration per deployment target
  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
  18. four separate config vars are annoying!

  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
  20. do not hardcode config vars, they might change suddenly!

  21. TIME FOR A DEPLOY! It's as easy as pie.

  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
  23. how did that work?

  24. V. BUILD, RELEASE, RUN

  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.
  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.
  27. X. DEV/PROD PARITY

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

    similar as possible.
  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
  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
  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!
  32. VI. PROCESSES

  33. VI. PROCESSES Execute the app as one or more stateless

    processes.
  34. VI. PROCESSES heroku-­‐python-­‐app  $  cat  Procfile   worker:  python  worker.py

      web:  gunicorn  hello:app
  35. VI. PROCESSES heroku-­‐node-­‐app  $  cat  Procfile   worker:  node  worker.js

      web:  node  index.js
  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
  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
  38. VI. PROCESSES heroku-­‐php-­‐app  $  cat  Procfile   worker:  php  background.php

      web:  php  -­‐S  0.0.0.0:$PORT
  39. PHP needs a dedicated web server

  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
  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...
  42. (or you use your good old VirtualHost setup)

  43. VIII. CONCURRENCY

  44. VIII. CONCURRENCY Scale out via the process model.

  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.
  46. XII. ADMIN PROCESSES

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

    processes.
  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!
  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!
  50. IV. BACKING SERVICES

  51. IV. BACKING SERVICES Treat backing services as attached resources.

  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/
  53. DEMO TIME!

  54. http://start.heroku.com/php & http://devcenter.heroku.com/categories/php

  55. ONE MORE THING...

  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
  57. The End

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

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