$30 off During Our Annual Pro Sale. View Details »

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

David Zuelke
February 06, 2015

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

Presentation given at SunshinePHP 2015 in Miami, Florida.

David Zuelke

February 06, 2015
Tweet

More Decks by David Zuelke

Other Decks in Programming

Transcript

  1. THE TWELVE-FACTOR APP
    Best Practices for Modern Web Applications

    View Slide

  2. David Zuelke

    View Slide

  3. View Slide

  4. View Slide

  5. @dzuelke

    View Slide

  6. “The Twelve-Factor App”

    View Slide

  7. “The Twelve-Factor App”

    View Slide

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

    View Slide

  9. Its goals are
    scalability,
    maintainability,
    portability.

    View Slide

  10. 12factor.net

    View Slide

  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");

    View Slide

  12. II. DEPENDENCIES
    Applications have explicitly declared dependencies.

    View Slide

  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"  
       }  
    }

    View Slide

  14. do not check your vendors/ directory into Git

    View Slide

  15. III. CONFIGURATION
    Store config in the environment.

    View Slide

  16. III. CONFIGURATION
    Store config in the environment.
    Assumption:
    same code but different configuration per deployment target

    View Slide

  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

    View Slide

  18. four separate config vars are annoying!

    View Slide

  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:[email protected]:827

    View Slide

  20. do not hardcode config vars, they might change suddenly!

    View Slide

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

    View Slide

  22. $  heroku  create  dz-­‐php-­‐test  
    Creating  dz-­‐php-­‐test...  done,  stack  is  cedar-­‐14  
    http://dz-­‐php-­‐test.herokuapp.com/  |  [email protected]: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

    View Slide

  23. how did that work?

    View Slide

  24. V. BUILD, RELEASE, RUN

    View Slide

  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.

    View Slide

  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.

    View Slide

  27. X. DEV/PROD PARITY

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  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!

    View Slide

  32. VI. PROCESSES

    View Slide

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

    View Slide

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

    View Slide

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

    View Slide

  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

    View Slide

  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

    View Slide

  38. VI. PROCESSES
    heroku-­‐php-­‐app  $  cat  Procfile  
    worker:  php  background.php  
    web:  php  -­‐S  0.0.0.0:$PORT

    View Slide

  39. PHP needs a dedicated web server

    View Slide

  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

    View Slide

  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...

    View Slide

  42. (or you use your good old VirtualHost setup)

    View Slide

  43. VIII. CONCURRENCY

    View Slide

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

    View Slide

  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.

    View Slide

  46. XII. ADMIN PROCESSES

    View Slide

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

    View Slide

  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!

    View Slide

  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!

    View Slide

  50. IV. BACKING SERVICES

    View Slide

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

    View Slide

  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:[email protected]:9092/

    View Slide

  53. DEMO TIME!

    View Slide

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

    View Slide

  55. ONE MORE THING...

    View Slide

  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

    View Slide

  57. The End

    View Slide

  58. THE TWELVE-
    FACTOR APP
    Thanks for listening! Contact:
    @dzuelke & [email protected].
    rate my talk
    on joind.in!

    View Slide