├── .editorconfig ├── .gitignore ├── LICENSE ├── README.md └── manuscript ├── 00-introduction.md ├── 01-automate.md ├── 02-goals.md ├── 03-environments.md ├── 04-atomic-deploys.md ├── 05-tools.md ├── 06-version-control.md ├── 07-dependencies.md ├── 08-database-migrations.md ├── 09-running-tests.md ├── 10-logs-and-notifications.md ├── 11-conclusion.md ├── Book.txt ├── Sample.txt ├── code ├── chapter-3 │ └── env-function.php ├── chapter-5 │ ├── buildfile.xml │ ├── capistrano-app │ │ ├── Capfile │ │ └── config │ │ │ ├── deploy.rb │ │ │ └── deploy │ │ │ ├── production.rb │ │ │ └── staging.rb │ ├── git-hooks-atomic-deploy │ │ ├── post-receive │ │ └── post-update │ ├── phing-atomic-deploy │ │ └── buildfile.xml │ ├── post-receive │ ├── post-update │ ├── rocketeer-app │ │ └── .rocketeer │ │ │ ├── config.php │ │ │ ├── hooks.php │ │ │ ├── logs │ │ │ └── .gitignore │ │ │ ├── paths.php │ │ │ ├── remote.php │ │ │ ├── scm.php │ │ │ ├── stages.php │ │ │ └── strategies.php │ ├── targets.xml │ └── tasks.xml └── chapter-8 │ ├── .gitignore │ ├── composer.json │ ├── composer.lock │ ├── migrations │ ├── 20150817080530_create_users_table.php │ ├── 20150818081646_add_created_at_to_users_table.php │ ├── 20150831053347_add_username_to_users_table.php │ └── 20150831053404_add_unique_username_index_on_users_table.php │ ├── phinx.yml │ └── scripts │ ├── PhinxBatch.php │ ├── migrate.php │ └── rollback.php └── images └── title_page.jpg /.editorconfig: -------------------------------------------------------------------------------- 1 | # EditorConfig helps developers define and maintain consistent 2 | # coding styles between different editors and IDEs 3 | # editorconfig.org 4 | 5 | root = true 6 | 7 | [*] 8 | indent_style = space 9 | indent_size = 2 10 | 11 | end_of_line = lf 12 | charset = utf-8 13 | trim_trailing_whitespace = true 14 | insert_final_newline = true 15 | 16 | [*.xml] 17 | indent_size = 4 18 | 19 | [*.php] 20 | indent_size = 4 21 | 22 | [*.md] 23 | trim_trailing_whitespace = false -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | /Icon* 2 | .DS_Thumb 3 | .dropbox 4 | /preview 5 | /published 6 | /proofread 7 | /manuscript/Subset.txt 8 | _toc.txt 9 | sync-to-dropbox 10 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Attribution-NonCommercial-ShareAlike 4.0 International 2 | 3 | ======================================================================= 4 | 5 | Creative Commons Corporation ("Creative Commons") is not a law firm and 6 | does not provide legal services or legal advice. Distribution of 7 | Creative Commons public licenses does not create a lawyer-client or 8 | other relationship. Creative Commons makes its licenses and related 9 | information available on an "as-is" basis. Creative Commons gives no 10 | warranties regarding its licenses, any material licensed under their 11 | terms and conditions, or any related information. Creative Commons 12 | disclaims all liability for damages resulting from their use to the 13 | fullest extent possible. 14 | 15 | Using Creative Commons Public Licenses 16 | 17 | Creative Commons public licenses provide a standard set of terms and 18 | conditions that creators and other rights holders may use to share 19 | original works of authorship and other material subject to copyright 20 | and certain other rights specified in the public license below. The 21 | following considerations are for informational purposes only, are not 22 | exhaustive, and do not form part of our licenses. 23 | 24 | Considerations for licensors: Our public licenses are 25 | intended for use by those authorized to give the public 26 | permission to use material in ways otherwise restricted by 27 | copyright and certain other rights. Our licenses are 28 | irrevocable. Licensors should read and understand the terms 29 | and conditions of the license they choose before applying it. 30 | Licensors should also secure all rights necessary before 31 | applying our licenses so that the public can reuse the 32 | material as expected. Licensors should clearly mark any 33 | material not subject to the license. This includes other CC- 34 | licensed material, or material used under an exception or 35 | limitation to copyright. More considerations for licensors: 36 | wiki.creativecommons.org/Considerations_for_licensors 37 | 38 | Considerations for the public: By using one of our public 39 | licenses, a licensor grants the public permission to use the 40 | licensed material under specified terms and conditions. If 41 | the licensor's permission is not necessary for any reason--for 42 | example, because of any applicable exception or limitation to 43 | copyright--then that use is not regulated by the license. Our 44 | licenses grant only permissions under copyright and certain 45 | other rights that a licensor has authority to grant. Use of 46 | the licensed material may still be restricted for other 47 | reasons, including because others have copyright or other 48 | rights in the material. A licensor may make special requests, 49 | such as asking that all changes be marked or described. 50 | Although not required by our licenses, you are encouraged to 51 | respect those requests where reasonable. More_considerations 52 | for the public: 53 | wiki.creativecommons.org/Considerations_for_licensees 54 | 55 | ======================================================================= 56 | 57 | Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International 58 | Public License 59 | 60 | By exercising the Licensed Rights (defined below), You accept and agree 61 | to be bound by the terms and conditions of this Creative Commons 62 | Attribution-NonCommercial-ShareAlike 4.0 International Public License 63 | ("Public License"). To the extent this Public License may be 64 | interpreted as a contract, You are granted the Licensed Rights in 65 | consideration of Your acceptance of these terms and conditions, and the 66 | Licensor grants You such rights in consideration of benefits the 67 | Licensor receives from making the Licensed Material available under 68 | these terms and conditions. 69 | 70 | 71 | Section 1 -- Definitions. 72 | 73 | a. Adapted Material means material subject to Copyright and Similar 74 | Rights that is derived from or based upon the Licensed Material 75 | and in which the Licensed Material is translated, altered, 76 | arranged, transformed, or otherwise modified in a manner requiring 77 | permission under the Copyright and Similar Rights held by the 78 | Licensor. For purposes of this Public License, where the Licensed 79 | Material is a musical work, performance, or sound recording, 80 | Adapted Material is always produced where the Licensed Material is 81 | synched in timed relation with a moving image. 82 | 83 | b. Adapter's License means the license You apply to Your Copyright 84 | and Similar Rights in Your contributions to Adapted Material in 85 | accordance with the terms and conditions of this Public License. 86 | 87 | c. BY-NC-SA Compatible License means a license listed at 88 | creativecommons.org/compatiblelicenses, approved by Creative 89 | Commons as essentially the equivalent of this Public License. 90 | 91 | d. Copyright and Similar Rights means copyright and/or similar rights 92 | closely related to copyright including, without limitation, 93 | performance, broadcast, sound recording, and Sui Generis Database 94 | Rights, without regard to how the rights are labeled or 95 | categorized. For purposes of this Public License, the rights 96 | specified in Section 2(b)(1)-(2) are not Copyright and Similar 97 | Rights. 98 | 99 | e. Effective Technological Measures means those measures that, in the 100 | absence of proper authority, may not be circumvented under laws 101 | fulfilling obligations under Article 11 of the WIPO Copyright 102 | Treaty adopted on December 20, 1996, and/or similar international 103 | agreements. 104 | 105 | f. Exceptions and Limitations means fair use, fair dealing, and/or 106 | any other exception or limitation to Copyright and Similar Rights 107 | that applies to Your use of the Licensed Material. 108 | 109 | g. License Elements means the license attributes listed in the name 110 | of a Creative Commons Public License. The License Elements of this 111 | Public License are Attribution, NonCommercial, and ShareAlike. 112 | 113 | h. Licensed Material means the artistic or literary work, database, 114 | or other material to which the Licensor applied this Public 115 | License. 116 | 117 | i. Licensed Rights means the rights granted to You subject to the 118 | terms and conditions of this Public License, which are limited to 119 | all Copyright and Similar Rights that apply to Your use of the 120 | Licensed Material and that the Licensor has authority to license. 121 | 122 | j. Licensor means the individual(s) or entity(ies) granting rights 123 | under this Public License. 124 | 125 | k. NonCommercial means not primarily intended for or directed towards 126 | commercial advantage or monetary compensation. For purposes of 127 | this Public License, the exchange of the Licensed Material for 128 | other material subject to Copyright and Similar Rights by digital 129 | file-sharing or similar means is NonCommercial provided there is 130 | no payment of monetary compensation in connection with the 131 | exchange. 132 | 133 | l. Share means to provide material to the public by any means or 134 | process that requires permission under the Licensed Rights, such 135 | as reproduction, public display, public performance, distribution, 136 | dissemination, communication, or importation, and to make material 137 | available to the public including in ways that members of the 138 | public may access the material from a place and at a time 139 | individually chosen by them. 140 | 141 | m. Sui Generis Database Rights means rights other than copyright 142 | resulting from Directive 96/9/EC of the European Parliament and of 143 | the Council of 11 March 1996 on the legal protection of databases, 144 | as amended and/or succeeded, as well as other essentially 145 | equivalent rights anywhere in the world. 146 | 147 | n. You means the individual or entity exercising the Licensed Rights 148 | under this Public License. Your has a corresponding meaning. 149 | 150 | 151 | Section 2 -- Scope. 152 | 153 | a. License grant. 154 | 155 | 1. Subject to the terms and conditions of this Public License, 156 | the Licensor hereby grants You a worldwide, royalty-free, 157 | non-sublicensable, non-exclusive, irrevocable license to 158 | exercise the Licensed Rights in the Licensed Material to: 159 | 160 | a. reproduce and Share the Licensed Material, in whole or 161 | in part, for NonCommercial purposes only; and 162 | 163 | b. produce, reproduce, and Share Adapted Material for 164 | NonCommercial purposes only. 165 | 166 | 2. Exceptions and Limitations. For the avoidance of doubt, where 167 | Exceptions and Limitations apply to Your use, this Public 168 | License does not apply, and You do not need to comply with 169 | its terms and conditions. 170 | 171 | 3. Term. The term of this Public License is specified in Section 172 | 6(a). 173 | 174 | 4. Media and formats; technical modifications allowed. The 175 | Licensor authorizes You to exercise the Licensed Rights in 176 | all media and formats whether now known or hereafter created, 177 | and to make technical modifications necessary to do so. The 178 | Licensor waives and/or agrees not to assert any right or 179 | authority to forbid You from making technical modifications 180 | necessary to exercise the Licensed Rights, including 181 | technical modifications necessary to circumvent Effective 182 | Technological Measures. For purposes of this Public License, 183 | simply making modifications authorized by this Section 2(a) 184 | (4) never produces Adapted Material. 185 | 186 | 5. Downstream recipients. 187 | 188 | a. Offer from the Licensor -- Licensed Material. Every 189 | recipient of the Licensed Material automatically 190 | receives an offer from the Licensor to exercise the 191 | Licensed Rights under the terms and conditions of this 192 | Public License. 193 | 194 | b. Additional offer from the Licensor -- Adapted Material. 195 | Every recipient of Adapted Material from You 196 | automatically receives an offer from the Licensor to 197 | exercise the Licensed Rights in the Adapted Material 198 | under the conditions of the Adapter's License You apply. 199 | 200 | c. No downstream restrictions. You may not offer or impose 201 | any additional or different terms or conditions on, or 202 | apply any Effective Technological Measures to, the 203 | Licensed Material if doing so restricts exercise of the 204 | Licensed Rights by any recipient of the Licensed 205 | Material. 206 | 207 | 6. No endorsement. Nothing in this Public License constitutes or 208 | may be construed as permission to assert or imply that You 209 | are, or that Your use of the Licensed Material is, connected 210 | with, or sponsored, endorsed, or granted official status by, 211 | the Licensor or others designated to receive attribution as 212 | provided in Section 3(a)(1)(A)(i). 213 | 214 | b. Other rights. 215 | 216 | 1. Moral rights, such as the right of integrity, are not 217 | licensed under this Public License, nor are publicity, 218 | privacy, and/or other similar personality rights; however, to 219 | the extent possible, the Licensor waives and/or agrees not to 220 | assert any such rights held by the Licensor to the limited 221 | extent necessary to allow You to exercise the Licensed 222 | Rights, but not otherwise. 223 | 224 | 2. Patent and trademark rights are not licensed under this 225 | Public License. 226 | 227 | 3. To the extent possible, the Licensor waives any right to 228 | collect royalties from You for the exercise of the Licensed 229 | Rights, whether directly or through a collecting society 230 | under any voluntary or waivable statutory or compulsory 231 | licensing scheme. In all other cases the Licensor expressly 232 | reserves any right to collect such royalties, including when 233 | the Licensed Material is used other than for NonCommercial 234 | purposes. 235 | 236 | 237 | Section 3 -- License Conditions. 238 | 239 | Your exercise of the Licensed Rights is expressly made subject to the 240 | following conditions. 241 | 242 | a. Attribution. 243 | 244 | 1. If You Share the Licensed Material (including in modified 245 | form), You must: 246 | 247 | a. retain the following if it is supplied by the Licensor 248 | with the Licensed Material: 249 | 250 | i. identification of the creator(s) of the Licensed 251 | Material and any others designated to receive 252 | attribution, in any reasonable manner requested by 253 | the Licensor (including by pseudonym if 254 | designated); 255 | 256 | ii. a copyright notice; 257 | 258 | iii. a notice that refers to this Public License; 259 | 260 | iv. a notice that refers to the disclaimer of 261 | warranties; 262 | 263 | v. a URI or hyperlink to the Licensed Material to the 264 | extent reasonably practicable; 265 | 266 | b. indicate if You modified the Licensed Material and 267 | retain an indication of any previous modifications; and 268 | 269 | c. indicate the Licensed Material is licensed under this 270 | Public License, and include the text of, or the URI or 271 | hyperlink to, this Public License. 272 | 273 | 2. You may satisfy the conditions in Section 3(a)(1) in any 274 | reasonable manner based on the medium, means, and context in 275 | which You Share the Licensed Material. For example, it may be 276 | reasonable to satisfy the conditions by providing a URI or 277 | hyperlink to a resource that includes the required 278 | information. 279 | 3. If requested by the Licensor, You must remove any of the 280 | information required by Section 3(a)(1)(A) to the extent 281 | reasonably practicable. 282 | 283 | b. ShareAlike. 284 | 285 | In addition to the conditions in Section 3(a), if You Share 286 | Adapted Material You produce, the following conditions also apply. 287 | 288 | 1. The Adapter's License You apply must be a Creative Commons 289 | license with the same License Elements, this version or 290 | later, or a BY-NC-SA Compatible License. 291 | 292 | 2. You must include the text of, or the URI or hyperlink to, the 293 | Adapter's License You apply. You may satisfy this condition 294 | in any reasonable manner based on the medium, means, and 295 | context in which You Share Adapted Material. 296 | 297 | 3. You may not offer or impose any additional or different terms 298 | or conditions on, or apply any Effective Technological 299 | Measures to, Adapted Material that restrict exercise of the 300 | rights granted under the Adapter's License You apply. 301 | 302 | 303 | Section 4 -- Sui Generis Database Rights. 304 | 305 | Where the Licensed Rights include Sui Generis Database Rights that 306 | apply to Your use of the Licensed Material: 307 | 308 | a. for the avoidance of doubt, Section 2(a)(1) grants You the right 309 | to extract, reuse, reproduce, and Share all or a substantial 310 | portion of the contents of the database for NonCommercial purposes 311 | only; 312 | 313 | b. if You include all or a substantial portion of the database 314 | contents in a database in which You have Sui Generis Database 315 | Rights, then the database in which You have Sui Generis Database 316 | Rights (but not its individual contents) is Adapted Material, 317 | including for purposes of Section 3(b); and 318 | 319 | c. You must comply with the conditions in Section 3(a) if You Share 320 | all or a substantial portion of the contents of the database. 321 | 322 | For the avoidance of doubt, this Section 4 supplements and does not 323 | replace Your obligations under this Public License where the Licensed 324 | Rights include other Copyright and Similar Rights. 325 | 326 | 327 | Section 5 -- Disclaimer of Warranties and Limitation of Liability. 328 | 329 | a. UNLESS OTHERWISE SEPARATELY UNDERTAKEN BY THE LICENSOR, TO THE 330 | EXTENT POSSIBLE, THE LICENSOR OFFERS THE LICENSED MATERIAL AS-IS 331 | AND AS-AVAILABLE, AND MAKES NO REPRESENTATIONS OR WARRANTIES OF 332 | ANY KIND CONCERNING THE LICENSED MATERIAL, WHETHER EXPRESS, 333 | IMPLIED, STATUTORY, OR OTHER. THIS INCLUDES, WITHOUT LIMITATION, 334 | WARRANTIES OF TITLE, MERCHANTABILITY, FITNESS FOR A PARTICULAR 335 | PURPOSE, NON-INFRINGEMENT, ABSENCE OF LATENT OR OTHER DEFECTS, 336 | ACCURACY, OR THE PRESENCE OR ABSENCE OF ERRORS, WHETHER OR NOT 337 | KNOWN OR DISCOVERABLE. WHERE DISCLAIMERS OF WARRANTIES ARE NOT 338 | ALLOWED IN FULL OR IN PART, THIS DISCLAIMER MAY NOT APPLY TO YOU. 339 | 340 | b. TO THE EXTENT POSSIBLE, IN NO EVENT WILL THE LICENSOR BE LIABLE 341 | TO YOU ON ANY LEGAL THEORY (INCLUDING, WITHOUT LIMITATION, 342 | NEGLIGENCE) OR OTHERWISE FOR ANY DIRECT, SPECIAL, INDIRECT, 343 | INCIDENTAL, CONSEQUENTIAL, PUNITIVE, EXEMPLARY, OR OTHER LOSSES, 344 | COSTS, EXPENSES, OR DAMAGES ARISING OUT OF THIS PUBLIC LICENSE OR 345 | USE OF THE LICENSED MATERIAL, EVEN IF THE LICENSOR HAS BEEN 346 | ADVISED OF THE POSSIBILITY OF SUCH LOSSES, COSTS, EXPENSES, OR 347 | DAMAGES. WHERE A LIMITATION OF LIABILITY IS NOT ALLOWED IN FULL OR 348 | IN PART, THIS LIMITATION MAY NOT APPLY TO YOU. 349 | 350 | c. The disclaimer of warranties and limitation of liability provided 351 | above shall be interpreted in a manner that, to the extent 352 | possible, most closely approximates an absolute disclaimer and 353 | waiver of all liability. 354 | 355 | 356 | Section 6 -- Term and Termination. 357 | 358 | a. This Public License applies for the term of the Copyright and 359 | Similar Rights licensed here. However, if You fail to comply with 360 | this Public License, then Your rights under this Public License 361 | terminate automatically. 362 | 363 | b. Where Your right to use the Licensed Material has terminated under 364 | Section 6(a), it reinstates: 365 | 366 | 1. automatically as of the date the violation is cured, provided 367 | it is cured within 30 days of Your discovery of the 368 | violation; or 369 | 370 | 2. upon express reinstatement by the Licensor. 371 | 372 | For the avoidance of doubt, this Section 6(b) does not affect any 373 | right the Licensor may have to seek remedies for Your violations 374 | of this Public License. 375 | 376 | c. For the avoidance of doubt, the Licensor may also offer the 377 | Licensed Material under separate terms or conditions or stop 378 | distributing the Licensed Material at any time; however, doing so 379 | will not terminate this Public License. 380 | 381 | d. Sections 1, 5, 6, 7, and 8 survive termination of this Public 382 | License. 383 | 384 | 385 | Section 7 -- Other Terms and Conditions. 386 | 387 | a. The Licensor shall not be bound by any additional or different 388 | terms or conditions communicated by You unless expressly agreed. 389 | 390 | b. Any arrangements, understandings, or agreements regarding the 391 | Licensed Material not stated herein are separate from and 392 | independent of the terms and conditions of this Public License. 393 | 394 | 395 | Section 8 -- Interpretation. 396 | 397 | a. For the avoidance of doubt, this Public License does not, and 398 | shall not be interpreted to, reduce, limit, restrict, or impose 399 | conditions on any use of the Licensed Material that could lawfully 400 | be made without permission under this Public License. 401 | 402 | b. To the extent possible, if any provision of this Public License is 403 | deemed unenforceable, it shall be automatically reformed to the 404 | minimum extent necessary to make it enforceable. If the provision 405 | cannot be reformed, it shall be severed from this Public License 406 | without affecting the enforceability of the remaining terms and 407 | conditions. 408 | 409 | c. No term or condition of this Public License will be waived and no 410 | failure to comply consented to unless expressly agreed to by the 411 | Licensor. 412 | 413 | d. Nothing in this Public License constitutes or may be interpreted 414 | as a limitation upon, or waiver of, any privileges and immunities 415 | that apply to the Licensor or You, including from the legal 416 | processes of any jurisdiction or authority. 417 | 418 | ======================================================================= 419 | 420 | Creative Commons is not a party to its public licenses. 421 | Notwithstanding, Creative Commons may elect to apply one of its public 422 | licenses to material it publishes and in those instances will be 423 | considered the "Licensor." Except for the limited purpose of indicating 424 | that material is shared under a Creative Commons public license or as 425 | otherwise permitted by the Creative Commons policies published at 426 | creativecommons.org/policies, Creative Commons does not authorize the 427 | use of the trademark "Creative Commons" or any other trademark or logo 428 | of Creative Commons without its prior written consent including, 429 | without limitation, in connection with any unauthorized modifications 430 | to any of its public licenses or any other arrangements, 431 | understandings, or agreements concerning use of licensed material. For 432 | the avoidance of doubt, this paragraph does not form part of the public 433 | licenses. 434 | 435 | Creative Commons may be contacted at creativecommons.org. 436 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Deploying PHP Applications 2 | ========================== 3 | 4 | This repository contains the entire book [Deploying PHP applications](https://leanpub.com/deploying-php-applications) with code samples. If you enjoy the book, please consider [buying it on Leanpub](https://leanpub.com/deploying-php-applications)! 5 | 6 | You can contribute by [creating issues](https://github.com/modess/deploying-php-applications/issues/new) for reporting errors and typos in it. Or by forking the repository and creating a PR. All help is appreciated! 7 | 8 | ![Deploying PHP applications](https://s3.amazonaws.com/titlepages.leanpub.com/deploying-php-applications/large?1407753697) 9 | 10 | Creative Commons License
Deploying PHP Applications by Niklas Modess is licensed under a Creative Commons Attribution-NonCommercial-ShareAlike 4.0 International License. 11 | 12 | ## Contributors 13 | 14 | In chronological order: 15 | 16 | * simpgeek <[https://github.com/simpgeek](https://github.com/simpgeek)> 17 | * begumserraaydin <[https://github.com/begumserraaydin](https://github.com/begumserraaydin)> 18 | * mathieuimbert <[https://github.com/mathieuimbert](https://github.com/mathieuimbert)> 19 | * DivineDominion <[https://github.com/DivineDominion](https://github.com/DivineDominion)> 20 | 21 | * *[Your name or handle] <[email or website]>* 22 | 23 | ## Changelog 24 | 25 | ### 1.0.1 - 2016-04-09 26 | 27 | * Updated Phing section to clarify it's a tool written in PHP 28 | * Updated symlink target location in Phing code example 29 | 30 | ### 1.0 - 2016-01-06 31 | 32 | * Added chapter 9 - "Running tests" 33 | * Added chapter 10 - "Logs and notifications" 34 | * Added "Conclusion" 35 | 36 | ### 0.1 - 2015-10-05 37 | 38 | * Open sourced the book 39 | * Added to chapter 3 - "Environments" 40 | - Environment variables 41 | - Server access 42 | * Added to chapter 5 - "Tools" 43 | - Capistrano 44 | - Rocketeer 45 | * Added chapter 7 - "Dependencies" 46 | * Added chapter 8 - "Database migrations" 47 | -------------------------------------------------------------------------------- /manuscript/00-introduction.md: -------------------------------------------------------------------------------- 1 | # Introduction 2 | 3 | ## Background 4 | 5 | The PHP language is in an interesting phase right now. After lagging behind on the more traditional software development practices for too long. Practices that have been more or less considered as given in other communities. Continuous integration, package management, dependency injection and adopting object oriented programming to its full extent to name some. But as of PHP 5.3 (released on 30 June 2009) there are no more excuses to why you can't write modern and clean code. The community has risen up to the challenge. A great number of people have stepped forward, teaching and building tools for accomplishing these practices. 6 | 7 | Yet we seem to forget deployment in this. It's time to bring it to the table for discussion. Great tools and services are out there. But few resources are available on how to set up, maintain and optimize your deployment process. In this book I hope to shed some light on what is important and how you achieve it. 8 | 9 | Deploying is not about pushing changes to a production server, but an important part of the [software development process](http://en.wikipedia.org/wiki/Software_development_process). You need put thought in to each step. Applications are often unique little snowflakes and you need to tend to its special needs. When working in a team is when you most of all need a great deployment process. The team should work in certain ways to enable the process to be beneficial for all parties involved. 10 | 11 | Every software application has a life cycle that the deployment process supports and is necessary for. You want to be able to maintain the application, roll out new features and do bug fixes without constant pain and headaches while doing it. You should be able to manage your branches, push your code, run the appropriate tests, migrate your database and deploy the changes in a fast and confident manner. With a good deployment process and a work flow that enables this, it will be a breeze. If you can do a fast and easy rollback too, your confidence in pushing code will benefit a lot. 12 | 13 | In a more and more agile software development world, being able to deploy is important. Release cycles are getting shorter and shorter and some organisations even push it to the limit with [continuous delivery](http://en.wikipedia.org/wiki/Continuous_delivery). In an environment with short release cycles, the importance of the deployment process intensifies. Not being able to deploy or rollback fast enough could end up slowing down your entire development process. 14 | 15 | ## Who it's for 16 | 17 | You are already familiar with PHP and you're not afraid of the command line. But you could also be a manager of a software development team that is a part of deployment on a regular basis. 18 | 19 | Legacy is not solely a code issue but a process issue as well. Are you looking to streamline your deployment process or you want to scrap your current one and start of fresh? Then this book is for you. 20 | 21 | ## Outside its scope 22 | 23 | I consider server provisioning an almost crucial part of deploying your application but it's too big of a topic for this book. It could without a doubt be a book on its own. I also don't want this book to focus on any framework or tool but keep the content and name broad instead of something like *Deploying PHP applications to Amazon Web Services with Ansible*. 24 | 25 | The tools and commands used will be outside the scope unless it's a deployment tool (then it will have a dedicated section). I will make examples with Git, Composer, Grunt, PHPUnit and other tools. If you want to learn more about the tools there are extensive amount of books, screencasts and blog posts to find, Google is your friend. 26 | 27 | ## Assumptions 28 | 29 | I know it's not nice to make assumptions about people, or software. I'm still going to do it to some extent in this book. I will make them when I approach examples, but I'll not judge you, your team, or your application in any way. 30 | 31 | #### Where you deploy to 32 | 33 | You will need a hosting environment that you are some what in control of. If you are not able to install software or run commands it will limit you in what you can achieve. Whether it's a hosted server, co-location server or a VPS does not matter. To use everything in this book you need control over it for installing software, changing configuration, etc. 34 | 35 | #### Git 36 | 37 | The base of some topics discussed will use Git as version control. Why? Because I think it enables work flows that is best suitable for a good deployment process. There's a chapter on the topic of Git for version control and branching strategy for a good deployment process. I could've named it *Git version control*. But I'll leave the name without Git in it since there are perhaps a lot in there that you can apply to other version control systems as well. Other than Git I have worked with Subversion and Perforce but when I found Git and started incorporating it in my work flow I have never looked back. 38 | 39 | #### Both ends 40 | 41 | There will also be an assumption about your application that it's not a pure backend application. If your application is a REST API for example with no frontend, it will not matter though. I will give some general examples on how to manage builds for your frontend as well, but all the commands used will be arbitrary. 42 | 43 | ## About the author 44 | 45 | I have been developing PHP applications for over 15 years now. During this time I've developed and deployed a great variety of applications. The scale of these applications have been from a few hundred users to over 250 million users. 46 | 47 | Oh, by the way I'm from Stockholm, Sweden. That means I'm writing a book in my second language, and I would appreciate all the help I can get when it comes to spelling and grammar. If you find anything, please create an issue in [this repository](https://github.com/modess/deploying-php-applications) or fork it, fix it and send me a pull request. Thank you! 48 | 49 | ## Code samples 50 | 51 | In the [repository on github](https://github.com/modess/deploying-php-applications), you can find code samples structured by chapter. Any substantial amount of code used in the book is available there for reference and use. 52 | 53 | ## Thanks to 54 | 55 | My friend and talented designer extraordinaire **Joakim Unge** for the awesome cover image. Look at it, it's a fucking rocket ship! Nowadays he's a developer and you can find him at [https://joakimunge.se/](https://joakimunge.se/). 56 | 57 | --- 58 | 59 | My sister **Jenny Modess**, the talented copywriter, for proof reading from a non-technical perspective. Keeping my spelling, grammar and storytelling in check! 60 | -------------------------------------------------------------------------------- /manuscript/01-automate.md: -------------------------------------------------------------------------------- 1 | # 1. Automate, automate, automate 2 | 3 | I want to get this off the bat right away. The most crucial ingredient in your deployment process should be to **automate everything** to **minimize human errors**. If your deployment process involves manual steps, you're going to have a bad time and shit will hit the inevitable fan. Nobody is perfect. People can, and will, forget if they need to remember. 4 | 5 | An automated deployment process will boost the confidence for the person deploying. Knowing that it will take care of everything of is a major contributor to feeling safe during a deploy. Of course other unexpected issues can arise but with a flexible process with logging and notifications you can relax and figure out what went wrong. 6 | 7 | ## 1.1 One button to rule them all 8 | 9 | You should have **one** button to push or **one** command to run, to deploy your application. If you don't, something is wrong and you should automate all steps. Perhaps the single manual intervention I would consider okay is *"Are you sure? This will push stuff to production. [N/y]"*. This might be a bold statement but I truly believe in it. 10 | 11 | Even if you're the sole developer on a project and you're deploying the application each time, I still say it's bad practice having manual steps. When it comes to teams it becomes a lot worse. If not all team members are familiar with the deployment steps, they won't be able to deploy. Team members come and go. Whenever a new team member arrive they will need to learn how to deploy in the correct manner. Sure there can be documentation for it. But whenever someone is familiar enough with it they will start to deploy without it. 12 | 13 | ## 1.2 Example of a manual step 14 | 15 | Let's take an example where you have a revision number of your assets in code. I would say something along these lines are fairly common practice. I sure have seen something like this way to often. This revision number handles cache busting, so browsers do not keep serving old assets after a deploy. 16 | 17 | This is a constant in a class somewhere for managing static assets versions. 18 | 19 | {lang=php} 20 | ~~~~~~~~ 21 | class Assets 22 | { 23 | const REVISION = 14; 24 | 25 | // [...] 26 | } 27 | ~~~~~~~~ 28 | 29 | Then it's applied to serving the static assets. 30 | 31 | {lang=html} 32 | ~~~~~~~~ 33 | 34 | ~~~~~~~~ 35 | 36 | This is truly as bad of a manual step you could have. It's easy to forget since it requires a code change. If it's forgotten it might break the users' experience serving cached and outdated assets. A manual step where you have to run a command in connection to the deploy would be better since it would be easier to remember. When you are already have the command line in front of you will be more prone to remember it. 37 | 38 | Since it requires a code change another issue will occur. That is when you remember the step in the middle of the deploy before pushing changes to production. You stop yourself before pressing the button, screaming "Shit, the assets revision number!". Now you have to deal with changing the code, committing it and push the code through a new deploy. In a perfect world you would've already merged a release branch and tagged it (discussed in chapter 2), so how would you approach that now? Reset your repository, remove the tag, commit and create the release branch again? Or would you commit and push, knowing that it will break [traceability](http://en.wikipedia.org/wiki/Traceability#Software_development) for this specific deploy? This is a problem that goes away with automation. 39 | 40 | ## 1.3 Time is always a factor 41 | 42 | You might be prone to say that you don't have time automating all the trivial steps or commands. If you spend one hour automating a command that takes one second to run you would have to run that command 3601 times before you have saved time on it. Yes, I did the math. While this is true I would say that it isn't the whole truth. 43 | 44 | We need to take into account the time spent on dealing with issues arising when forgetting it. Time spent on cleaning up. Can you measure bad user experience when your application breaks or behaves incorrect? You can't. In the previous example you can't tell how much time that you will spend on correcting that error. Neither can you tell the impact on the users. If the problem isn't caught in time it could persist for a long time. 45 | -------------------------------------------------------------------------------- /manuscript/02-goals.md: -------------------------------------------------------------------------------- 1 | # 2. Goals {#chapter-goals} 2 | 3 | I hope you have decided that you want to improve your deployment process. Well, that is probably why you're reading this book at this moment. You should at this point establish some markers. Ask yourself the questions of where you are, and where you would like to be. These are essential questions in reaching goals. Between here and there you should also set some milestones, since sensing that you achieve something is always important. This is starting to sound like a self help book, but I'm of course talking about where your deployment process is and where you would like it to be. 4 | 5 | We have some topics we need to go through before we get to the actual list of goals. Understanding the background of the parts will help you better grok the big picture. 6 | 7 | ## 2.1 How it generally begins 8 | 9 | Take a deep breath and imagine a peaceful lake in your mind. No just kidding, stop with that. This is still not a self help book. 10 | 11 | Since the deployment process is like any other process in the software development cycle, it will mature in certain ways. It generally starts at a similar place. This place has seen a lot of developers and it will continue to see a lot of other developers pass by. What I want is to show you what kind of place that is and why you should try to avoid going there. The main reason that place is bad is that it's **at the end** of everything. I realize that I said that the beginning is at the end, but let me explain what I mean before you call me an idiot. 12 | 13 | An application starts with an idea. Then some wireframes (either mental or actual ones). Then perhaps some design, and of course some code. The first beta is ready for release. Wow that was fast, but anyway. Success, get the minimum viable product out there! This is where people stop and think "Oh right, we need some kind of hosting". Someone with a credit cards pays for the hosting and a developer has to deploy the code to it. Of course that's not a problem for the developer. But there is a big risk of this part not receiving the tender loving care it deserves and needs. No deployment tools is used, no automation is put into place. I'm not saying this is the case for all project, but it's a generally good estimation for most projects. 14 | 15 | Do you see the issue at hand here? **Deployment is in most cases an after thought**. It's something most feel they have to do, not want to do. Most wouldn't get out of bed earlier on a Monday because they are so eager to set up a sexy deployment process. This will often be a "fix things as you go" kind of process with quick and dirty fixes, duct taping that shit if it's necessary. This is something I refer to as *duct tape deployment*, and yes you can quote me on that. This is more than often a one man or one woman show. If someone else would like to deploy they wouldn't be able to. They would first have to ask the "one man/woman deploy army" how it's done, what the passwords are, which paths are used, where the database is and everything else that comes with it. Efficient? No. Sustainable? No. 16 | 17 | What we need to start with is making the deployment an important part in our applications' lives. It should be there for our application as a supporting and nurturing parent. Would you let your kid go to the first day of school without you being there? Of course not. The school system in the United States is fond of their *No Child Left Behind Act* and I would like to propose a *No Application Left Behind Act*. Where no applications gets left behind because of a bad deployment process. It might be a bad analogy, since they have a crappy education system in the United States. But I hope I got my point across anyway. 18 | 19 | ## 2.2 Maturity {#maturity} 20 | 21 | During your application's lifetime the deployment process will often mature. That it matures is a good thing, and sooner or later someone will probably realize that it needs to improve. In which ways it will mature is generally according to the following but not always in this particular order: 22 | 23 | * Documentation 24 | * Automation 25 | * Verification 26 | * Notification 27 | * Tests 28 | * Tools 29 | * Monitoring 30 | 31 | **Documentation**. This is what happens when the it transitions from one person deploying to multiple people deploying. It will be a natural step, since the deployment process will most often have manual steps, so you can't deploy without documentation. Somewhere a document or something similar will end up describing the different steps that describes the neccessary steps for deploying. 32 | 33 | **Automation**. Multiple people are now deploying, but stuff breaks because of inconsistency. Even if all manual steps are well documented, unexpected errors occur that fall outside the scope of the documentation. In these cases it will end up on the one woman/man deploy army, since they will have the answers. The response and solution to this is automation. It should be a humble effort to, in the end, automate everything in the process. 34 | 35 | **Verification**. The process is now automated enough to make anyone able to deploy in a consistent manner. But still it can break in numerous ways. The automation will handle errors so we can verify that the steps complete without errors. If any automation fails the deploy aborts, and displaying an appropriate message to the person deploying on what went wrong. 36 | 37 | **Notification**. With verification in place, making it notify the right people when it fails should be trivial. Having a silent fail deploy could be catastrophic. Deploying the wrong changes could also be troublesome. A notification on what you are deploying (branch, commit, etc) and that it did so without errors can be as useful as for a failing one. 38 | 39 | **Tools**. It seldom happens that a deployment process will use tools from the start. But a mature and stable enough process benefits from implementing a tool. That is becuase a tool is easier to extend, change or replace. Any previous scripting and documentation will become obsolete since the tools is providing that instead. 40 | 41 | **Monitoring**. This is a step few will reach, and is more of a *nice to have* than a *need to have*. It's about monitoring how the application and its environments responds to a deploy. It could check CPU usage, memory usage, error responses, I/O-operations, or any other metric your application can produce. If a deploy increases memory usage by 30% you might have a big problem on your hands. The goal here is to monitor and notify on big deviations for certain key metrics. 42 | 43 | ## 2.3 Agility 44 | 45 | The world around us as software developers is getting more and more agile. I'm talking about the world of [agile software development](http://en.wikipedia.org/wiki/Agile_software_development), not the regular outside world where people drink lattes and worry about their mortgages. More and more companies are jumping on the agile bandwagon and for a good reason. Some practices in this that are great, such as behaviour-driven development (BDD), test-driven development (TDD), pair programming, continuous integration, sprints, user stories and cross-functional teams to name some. 46 | 47 | > Agile: "Characterized by quickness, lightness, and ease of movement; nimble." 48 | 49 | This sums it up pretty well. You want your software development cycle to be fast, easy and adaptable, where you work in small increments and respond fast to feedback through iteration. At the beginning of 2001, a bunch of smart people got together to talk about lightweight development methods, and what they ultimately came up with was the publication [Manifesto for Agile Software Development](http://agilemanifesto.org/). This is the main part of it: 50 | 51 | > We are uncovering better ways of developing software by doing it and helping others do it. Through this work we have come to value: 52 | 53 | > **Individuals and interactions** over Processes and tools 54 | > 55 | > **Working software** over Comprehensive documentation 56 | > 57 | > **Customer collaboration** over Contract negotiation 58 | > 59 | > **Responding to change** over Following a plan 60 | > 61 | > That is, while there is value in the items on the right, we value the items on the left more. 62 | > 63 | > *Kent Beck, James Grenning, Robert C. Martin, Mike Beedle, Jim Highsmith, Steve Mellor, Arie van Bennekum, Andrew Hunt, Ken Schwaber, Alistair Cockburn, Ron Jeffries, Jeff Sutherland, Ward Cunningham, Jon Kern, Dave Thomas, Martin Fowler, Brian Marick © 2001, the above authors. This declaration may be freely copied in any form, but only in its entirety through this notice.* 64 | 65 | This is exciting stuff and if you take a look at what is valued in the items on the left, it's people, flexibility and speed. Flexibility and speed of the actual development and people is both about the people building the software and the people using the software. 66 | 67 | Now you might say that I have been talking about the deployment **process** a lot. And the manifesto states that it favours individuals and interactions over processes and tools. Yes, this is true; it also states that there is value in the items on the right and I consider the deployment process to be essential in supporting a true agile development process. The core in agility for software development is iteration, an ability to adapt fast and respond to feedback from people. How will you be able to do this efficiently without a good deployment process? If you iterate over a feature and spend two hours on it and then have to spend two hours deploying it, is that agile? I would argue no. Even if the deploy takes "only" 30 minutes, you need confidence in it. If there are manual steps or you can't do a quick and easy rollback, what is agile about it then? 68 | 69 | ## 2.4 Plan for a marathon 70 | 71 | What could be better in all this than doing a comparison with a marathon? In an agile world you work in sprints and if you plan your deployment process for the current sprint, your plan will be short sighted and situational. If you expect your application to live longer than for a few sprints, you need a plan for its future. Plan for a marathon, not a short sprint. 72 | 73 | Making estimations is hard whether you make it for your development time or your application's future. This is especially true in the agile world where adapting fast to feedback could change the direction of that future in a heartbeat. But we should at least try to do this. Try to make some predictions about where your application could end up. Coming up with a some scenarios that could occur will not harm you. 74 | 75 | Say you deploy your application to a VPS with limited resources and no real options to scale it. If you predict there is a possibility your application would get featured on Hacker News, Reddit, Product Hunt or Slashdot, and the traffic goes through the roof, now what? Do you have a deployment process that is good enough for quickly moving your application to a new hosting provider? Does your application and deployment process support running on multiple nodes? Just to be clear, I'm not saying you should make your deployment process deal with all the possible scenarios since that would be time inefficient. What I'm saying is that you should keep those scenarios in the back of your head, while you plan for and implement with a "if X happens, I can change this to fit it"-mentality. Your deployment process should be agile too and try to make it so you can change things fast and easy. 76 | 77 | ## 2.5 Release cycles 78 | 79 | You can write how much code you like, but if you're not shipping any of it you're not contributing with any value. You and your team should have a goal of how often you want to release code. Whether it be once every month or four times a day, as long as you have a goal and a plan for it. If you do not have a goal for it you can't reach something. So set a goal if you do not have one and try to get there. Knowing your current and your wanted release cycle is important for planning your deployment process. 80 | 81 | I> By "shipping code" I refer to deploying it, from finishing a feature in your local environment to getting it out to the production server(s). 82 | 83 | There are a few ways in which we ship code and the different models of it can be summed up to: 84 | 85 | * I'm done, ship it. 86 | * The new version is done, ship it. 87 | * X amount of time has passed, ship it. 88 | * I pushed it, ship it. 89 | 90 | This is of course simplified and there are many variants of these out there in the wild. But let's discuss these individually to see what they're about. 91 | 92 | **I'm done, ship it**. We could call this *feature deploy* or *shotgun deploy* depending on how we feel about it. This is a typical model for when there is one or few developers for an application. It's an ad-hoc type of deploy and when you have finished and tested your feature you deploy it. I think this is a very underestimated type of deploy if you can make sure that you do not push changes someone else has made. Why should we wait for a ritualistic deploy? If you version control with a good branching strategy these types of ad-hoc deploys are definitely possible. For larger teams and applications this is something I wouldn't recommend though. 93 | 94 | **The new version is done, ship it**. This would be when your software has reached a new version, either minor or major. The point here is that the deploy payload will probably be large and the development has been undergoing for a considerable amount of time. I would avoid this model like the plague when it comes to web applications. One of the great advantages of web applications is the ability to change the application quickly without having to go through build steps and knowing your users have updated their applications. On the web you ship your code and then your user doesn't have a choice. Why even deploy only after a minor version is completed? If you are doing this, one of the reasons could be that the deployment process is to complex. Fix it and stop pushing like this. 95 | 96 | **X amount of time has passed, ship it**. If your team is working in sprints this is probably how you deploy. Whenever a sprint is complete you deploy. The amount of time between deploys can vary depending on how long sprints you have of course and the main take here is that deploys happen on a recurring time, for example every other week on Mondays. I do not mind this model for deployment at all if you have reasonable sprint durations. If you have three month sprints it could be bad and should take a look at why you're doing this. But if you have sprints of perhaps 1-2 weeks, go for it. 97 | 98 | **I pushed it, ship it**. Also known as *continuous deployment*. Ah, the unicorn of deploys. Here everything is deployed when one or more commits are pushed to a location. This is done through (at least should be) a very complex system of automation with testing and monitoring. The topic of how you can accomplish this is not an easy one and I will not try to explain it here. 99 | 100 | One important point to make here is that being able to continuously deploy is living the dream. Very few will have time, knowledge and patience to set up a complete process of continuous deploys. However it's something that we can strive for even if you do not want to continuous deploy. Building a culture, environment and process where it's possible will benefit you tremendously. Having the correct tools in place for automation, monitoring, etc will never be a bad thing. If you could have it all and just flip off the switch that deploys everything automatically, you should have a sense of bliss and serenity. You've done it. 101 | 102 | ## 2.6 Technical debt and rot 103 | 104 | Technical debt and software rot is something well known and talked about in the developer community, but the concepts can be applied to more than code. We should take care of our deployment just as much as our code. When an ad-hoc deployment process is put in place we immediately start hoarding technical debt and when it starts to rot we have **shipping rot**. The problem with this kind of debt and rot is that it's more than usually exponential and self enforcing. Have you heard of the [broken window theory](http://en.wikipedia.org/wiki/Broken_windows_theory)? The TL;DR version is: when people see that a building has a broken window, they stop caring less about maintaining it and into the spiral it goes. This applies perfectly to software development where legacy code stays legacy and bad code breeds more bad code. The same goes for our deployment process: a bad process will stay bad or get worse. If you can stop that first window from breaking or quickly repair it, everyone else will care for that it stays that way. Having goals set is a great tool for quickly dealing with that broken window and if you at some point realize that there are many broken windows, you should perhaps fix them all in one big swoop. 105 | 106 | ## 2.7 The list {#goals-list} 107 | 108 | We can condense the goals in to a bullet list since everybody likes a clear and concise list, right? When reading the list, try to reflect on where you currently are at the different goals. 109 | 110 | * Automated 111 | * Responsive 112 | * Atomic 113 | * Reversible 114 | * Simple 115 | * Fast 116 | * Agnostic 117 | 118 | **Automated**. Have you read the first chapter? If not, do it. Not having an automated process is the root of all evil. 119 | 120 | **Responsive**. A good deployment process responds to what is happening. If an error occurs somewhere it should abort and notify you or the appropriate people somehow. Having steps fail silently in a chain can be very destructive for your application. 121 | 122 | **Atomic**. Nothing in your deploy should be able to break your application and you should comply with the concept of completing a build before serving it to any user. The transition between the previous and the new build should be as close to instant as possible. 123 | 124 | **Reversible**. Because it sounds better than "rollbackable". Being able to roll back is just as important as deploying your changes and this reverse process should also follow the list of goals. 125 | 126 | **Simple**. Everyone should understand it and use it. Everyone should feel confident and comfortable with it. This is true for deploying, making changes or extending the process. 127 | 128 | **Fast**. You want it to be fast because speed is key in being able to deploy often. Fast rollbacks combined with fast deploys is another key which spells confidence. If something breaks you can easily go back and fix things without that stress knowing production is in a broken state. 129 | 130 | **Agnostic**. Building a deployment process that is dependent on its environment could be devastating. Being able to only deploy to Amazon Web Services for example will be great until you want to switch provider. The application should happily be deployed anywhere and it should also be agnostic about who or what is deploying. 131 | -------------------------------------------------------------------------------- /manuscript/03-environments.md: -------------------------------------------------------------------------------- 1 | # 3. Environments 2 | 3 | Having multiple environments for your application is essential for quality. Each environment serve a certain purpose and represents a state of your application in some way. When I talk about an environment I mean in which your application lives, that could be separate servers or virtual machines. Having multiple environments running on one machine is not an issue if they are separated but they must be it in some way to prevent them from interfering with each other. If two of your environments are sharing the same database or cache, you could end up with unexpected results and even create bugs that didn't exist in the first place. 4 | 5 | ## 3.1 Repeatable 6 | 7 | Being told you're a "broken record" isn't fun but for your environments it's the best thing you could say. The most important thing to strive for with your environments is that they always have the same setup where operating system and software should be of the same version and have the same configuration. And in a best case scenario they are **repeatable through automation**. 8 | 9 | Many times have I encountered unexpected behaviour because of environments differing from each other. Some operating systems, CentOS for example, treat file names case sensitive while others like Ubuntu treat them case insensitive. This could end up with files not being read into your application. Another good example is how JSON is encoded/decoded; some environments will encode `0` to `"0"`. If your application is expecting an integer and will get a string then things can go wrong. 10 | 11 | Knowing that your application will behave the same in your various environments is great because then you do not have to think about it. *Removing uncertainty* is important for gaining confidence in your deployment process. 12 | 13 | ### Server provisioning 14 | 15 | I said before that this topic is outside the scope of this book but I still want to touch briefly on it. If you want to achieve repeatability in your environments to the point where all your environments are all the same: you need it. Server provisioning is automating your environments. Say you're firing up a new virtual instance or installing a new server, what are your next steps? Often they are: 16 | 17 | * Install software 18 | * Set configuration values for the software 19 | * Set up a service or application 20 | * Configure the service or application 21 | * Serve the service or application 22 | 23 | If you do this manually you will inevitably loose control of how to repeat it. Making sure that correct values are set in `php.ini` across all servers will never be maintainable if done in a manual fashion. The more environments you have, the tougher it will become. Need to update the file size upload limit on 78 server instances? Have fun. Perhaps get an intern. 24 | 25 | There are a number of tools for server provisioning. The current most popular ones are [Chef](https://www.getchef.com/), [Puppet](http://puppetlabs.com/) and [Ansible](http://www.ansible.com/). The latter have gained a lot of traction lately. I suggest you read up on them and take a look at what problems they try to solve and how they solve them. They can help you with taking your deployment process to the next level. 26 | 27 | ## 3.2 Configuration / environment variables 28 | 29 | An important aspect of environments is configuring your application for the environment it's running in. Separation of code and configuration is a part of the [twelve-factor app](http://12factor.net/config) and it explains the importance of this separation as: 30 | 31 | > Apps sometimes store config as constants in the code. This is a violation of twelve-factor, which requires strict separation of config from code. Config varies substantially across deploys, code does not. 32 | 33 | The distinction between code and configuration is important since your application should be able to run without depending on configuration in your repository. They state a simple but powerful question to this, which is *can you at any given time open source your application without compromising any credentials?* If you could in theory do this you have a strict separation of code and configuration and I'll show you how to do this with a simple and great tool called *PHP dotenv*. 34 | 35 | The goal is to supply your application with configuration that is outside of the repository and this could be any important hostnames or credentials for database connections, cache connections or third party services such as *S3*, *IronMQ* or *MailChimp*. 36 | 37 | ### PHP dotenv 38 | 39 | This tool is probably all you need for your configuration requirements, it's simple yet powerful. It does not require you to edit any virtual hosts in Nginx or Apache nor add or modify any PHP configuration values. All you do is create a file with variables in a key/value manner and it populates the global variables `$_ENV` and `$_SERVER` for you and also makes them accessible via the [`getenv()`](http://php.net/manual/en/function.getenv.php) function. 40 | 41 | Installation is done through Composer: 42 | 43 | composer require vlucas/phpdotenv 44 | 45 | Then you need to bootstrap the configuration loading. In this example we pass `__DIR__` to it which tries to locate the configuration file in the same directory as the executed file, but this could be replaced with any directory you want to store your configuration file in: 46 | 47 | {lang="php"} 48 | ~~~ 49 | $dotenv = new Dotenv\Dotenv(__DIR__); 50 | $dotenv->load(); 51 | ~~~ 52 | 53 | After this we create a file called `.env` in the directory we supplied and for the sake of an example we add our database configurations to it: 54 | 55 | ~~~ 56 | DATABASE_HOST=localhost 57 | DATABASE_USERNAME=user 58 | DATABASE_PASSWORD=password 59 | ~~~ 60 | 61 | We can now access these configuration values in our application and all three of these return *localhost*: 62 | 63 | {lang="php"} 64 | ~~~ 65 | $databaseHost = getenv('DATABASE_HOST'); 66 | $databaseHost = $_ENV['DATABASE_HOST']; 67 | $databaseHost = $_SERVER['DATABASE_HOST']; 68 | ~~~ 69 | 70 | I recommend using `getenv()` or even better writing a wrapper function that allows you to pass a default value if the configuration value isn't set (inspired by Laravel's [env()](https://github.com/laravel/framework/blob/a1dc78820d2dbf207dbdf0f7075f17f7021c4ee8/src/Illuminate/Foundation/helpers.php#L626) function). 71 | 72 | {lang="php"} 73 | <<(code/chapter-3/env-function.php) 74 | 75 | This allows you to fetch a configuration value or get a default value 76 | 77 | {lang="php"} 78 | ``` 79 | $databaseHost = env('DATABASE_HOST', 'localhost'); 80 | ``` 81 | 82 | Make sure your configuration file is ignored in your repository so it doesn't get committed. Remember the part where you should be able to, at least in theory, open source your application without compromising any credentials. 83 | 84 | I> ### Example configuration file 85 | I> 86 | I> For reference and easy setup create an example configuration file that you can copy and replace the values from. Create `.env.example` that contains all necessary keys you need for your application. 87 | 88 | ## 3.3 Local environment 89 | 90 | Having a local development environment is something you should strive for. With local I mean on your actual computer, not on a centralized server somewhere you access through a VPN service or similar. Being able to work on your code anywhere without an internet connection is nice. 91 | 92 | I always run my local development environment in a virtual machine with [Vagrant](http://www.vagrantup.com). This allows me to go haywire with everything and if I fuck up the environment badly enough I just destroy the virtual machine and provision it from scratch. It's great for testing things in your environment without having to worry about breaking something on your computer or for someone else. Want to test your application in a different PHP version for example? Install, test, reset. 93 | 94 | The main point is that you want to be able to work on your application whenever. With a local environment you can work on a flight across the atlantic and push/pull changes as soon as you get a connection to the internet. If you end up in a situation where you are dependent on an internet connection to work you'll curse yourself when you don't have one. 95 | 96 | Having your local environment repeatable is the number one tool for introducing new team members to your code. Imagine you start at a new job and all you do it `git clone ` and then a `vagrant up` and you're all set. Sounds like a dream doesn't it? Usually time is invested in writing documentation for setting up a local environment and that documentation is more than often outdated and no one really wants to take responsibility for keeping it updated. 97 | 98 | ## 3.4 Development environment 99 | 100 | The name of this one can be somewhat misleading. No development work should be done here but the name comes from the branching strategy in [chapter 6 on Version Control](#chapter-version-control). This is a common ground for features and bug fixes where all code that should be shared and tested by others will end up here. The first step after finishing something in your local environment will most often be to merge it to your main branch and push it to the development environment. 101 | 102 | It's a great place for early testing by yourself and your team. Getting an extra set of eyes on something early on can be a great quality tool. Code reviews should be performed before something is merged and pushed to this environment since it's a very effective tool for quality, knowledge sharing and stomping out some obvious errors. All developers will have certain domain knowledge and someone else could perhaps tell right away if your code won't work with a specific part of the domain. 103 | 104 | ## 3.5 Staging environment 105 | 106 | Your staging environment is very important and it should to the fullest extent possible duplicate your production environment. This is where you do your final testing of your code before you push it to production and serve your application to the users. 107 | 108 | With duplicate to the fullest extent I do not mean that it should connect to the production database for example. Having a database that gets updated with a production dump every 24 hours is good enough. Does your application have multiple database instances and perhaps connect to a cache cluster? The staging environment will need that as well. Is the application running on multiple nodes behind a load balancer? Set up your staging environment in the same say. Remember, **as close to the production environment as possible**. 109 | 110 | Being able to test against production data is sometimes necessary but be extremely careful when doing this for obvious reasons. Make sure you do not test features that for example could end up e-mailing users. Your staging environment should perhaps only allow outgoing e-mail to certain addresses that belongs to people in your team or in your company. Here is a simple example that logs the e-mail if it's not in production: 111 | 112 | {lang=php} 113 | ~~~ 114 | function sendEmail ($to, $from, $subject, $message) { 115 | $productionEnvironment = (getenv('ENVIRONMENT') === 'production'); 116 | $internalEmail = (strstr($to, '@yourapplication.com') !== false); 117 | 118 | if (!$productionEnvironment && !$internalEmail) { 119 | $log = sprintf( 120 | 'Sending "%s" e-mail to %s from %s', 121 | $subject, 122 | $to, 123 | $from 124 | ); 125 | error_log($log); 126 | 127 | return true; 128 | } 129 | 130 | $headers = 'From: ' . $from . "\r\n" . 131 | 'Reply-To: ' . $from; 132 | 133 | return mail($to, $subject, $message, $headers); 134 | } 135 | ~~~ 136 | 137 | ## 3.6 Production environment 138 | 139 | This one is quiet obvious and it's the server(s) serving your application to your end users. Whether it's one server or multiple ones behind load balancer; this is the endpoint for your users. 140 | 141 | It will contain an as stable as possible code base with code that is extensively tested and as bug free as possible. What is to be said here is that **it's for this environment we need a great deployment process**. All the effort we put in to the process will reflect on the quality and stability of your production environment. 142 | 143 | ## 3.7 Testing environment(s) 144 | 145 | Having one or multiple test environments is not obligatory but it is something that can bring a lot of benefits for your application. Depending on the size of your application it will make testing a lot easier. If you are able to set up test environment for specific branches for example, you can easily give access to testers for feature branches. 146 | 147 | There are a few things that are essential for having success with test environments: speed and repeatability. If you can without major effort set up a test environment and check out a certain branch, your testing possibilities will increase dramatically. When I say speed I would consider 30 minutes or less a reasonable amount of time. Longer than that and you can start to experience too big discrepancies in development speed and test environment speed. Also If you're not able to update your test environment with the latest changes it will hardly be worth your time. The loop here is to get quick feedback on your code and react to it. Fix the code, push to the test environment again and then get new feedback. 148 | 149 | ## 3.8 Simplify server access 150 | 151 | In the process of managing multiple servers you end up wanting to make access to them faster and easier. First of all creating host entries for your various servers will remove the need of looking up IP addresses. For some servers you won't need it since they will be publicly accessible through domains such as your production environment. But even for your production environment you might have servers for databases, load balancers, caches, etc and they might not be accessible through a public domain. But you will still need to access them on a regular basis for maintenance and debugging so create host entries for them. 152 | 153 | The second thing is setting up and maintaining your SSH configuration file. In this you can manage hosts, hostnames, keys, ports and everything else you need to simplify your access to the servers. Compare these commands and you'll realize that you can gain a lot by this: 154 | 155 | ``` 156 | ssh -i ~/.ssh/myapp-staging.key staging@123.123.1.9 -p 2223 157 | 158 | # instead when using configuration file: 159 | 160 | ssh myapp-staging 161 | ``` 162 | 163 | All we need for this is to create `~/.ssh/config` and add our configuration for our environments there. For the previous example it could look like this, with host entries added and expanded for covering multiple environments: 164 | 165 | ``` 166 | Host myapp-staging 167 | HostName staging.myapp.com 168 | Port 2223 169 | User staging 170 | IdentityFile ~/.ssh/myapp-staging.key 171 | 172 | Host myapp-staging-db 173 | HostName staging-db.myapp.com 174 | Port 2223 175 | User staging 176 | IdentityFile ~/.ssh/myapp-staging.key 177 | 178 | Host myapp-production 179 | HostName myapp.com 180 | Port 2224 181 | User production 182 | IdentityFile ~/.ssh/myapp-production.key 183 | 184 | Host myapp-production-db 185 | HostName db.myapp.com 186 | Port 2224 187 | User production 188 | IdentityFile ~/.ssh/myapp-production.key 189 | ``` 190 | 191 | As you can see, multiple hosts can be defined and you can create one for each of your servers that you manage often. This will save you lots of time in accessing the servers. There are even more cool stuff you can do with it such as specifying wild card hosts and more. You can do some further reading on the [man page for ssh_config](https://www.freebsd.org/cgi/man.cgi?query=ssh_config&sektion=5). 192 | -------------------------------------------------------------------------------- /manuscript/04-atomic-deploys.md: -------------------------------------------------------------------------------- 1 | # 4. Atomic deploys 2 | 3 | Once upon a time there was a team of developers working on a service for sending e-mail campaigns. Their goal was that the system would allow users to manage subscribers and e-mail templates, so that the user could create e-mail campaigns and send them to their subscribers. The service got its first early adopters and began delivering successful campaigns with happy users. The focus for the team was feature development, adding features that was requested by customers or the ones they felt needed for the service. 4 | 5 | The team had a simple infrastructure in place, and they had a plan for when the service (hopefully) had to scale up. They divided it in two logical components. The first component was the one presented to end users, a kind of a mishmash of CMS and CRM. This web component was responsible for creating the e-mails and sending them over to the mail server. The second component, the mail server, had a single responsibility of receiving pre-configured e-mails and sending them out. The components were deployed on two separate cloud instances and the plan was to scale horizontally if the service gained enough traction. When it came to deployment the lead developer would access the instances, pull down the latest changes and complete the steps necessary for a deploy. This was manual labor. 6 | 7 | The service continued to grow, more and more users signed up for it and started using it as their main e-mail marketing channel. Not only did more users sign up but also users with a lot larger subscriber lists migrated over from other similar services. To import subscribers the user had to copy and paste their list(s) into a textarea with one e-mail address on each row. This was insufficient for many users and the most requested feature became an improved importer, being able to upload files with tens, or even hundreds, of thousands subscribers to be imported. The team got to work and once they had a completed feature they rolled it out to the public and immediately notified users about their grand new feature. It became a frequently used feature, especially for users migrating from other services. 8 | 9 | A few weeks went by and suddenly the team started receiving e-mails and phone calls from annoyed users who had used their import feature. The users repeatedly claimed that when they had uploaded their file, they had to wait for a long time before anything happened and all of a sudden the import was canceled for no apparent reason. The team started a thorough investigation and they dove deep into the code and logs trying to reproduce the problem. They set disproportionate time and memory limits for code execution, but to no avail. Instead they tried looking at patterns for when in time this happened, could it be an issue when the load is high? They looked at different monitored values for their servers but couldn't find anything. But what they did find was that the e-mails and phone calls they received with complaints often happened every other week on Mondays. And usually continued to around Wednesday the same week. 10 | 11 | It now occurred to the team that the pattern followed their release cycle. They worked in two week sprints and deployed every other Monday. It was these Monday's that the problem occurred. Upon this realisation it became quite obvious what caused the issue. When deploying their application they always restarted the *php-fpm* service just in case. When the service restarted, all users who was running an import (a long running PHP process) would get their import canceled because all PHP processes were killed. It now occurred to the team that their deploy process wasn't *atomic*. 12 | 13 | ## 4.1 What is atomicity? 14 | 15 | What is an atomic deploy? It's all about hiding things from the rest of the world. Let's take a look at one definition: 16 | 17 | > In concurrent programming, an operation (or set of operations) is atomic, linearizable, indivisible or uninterruptible if it appears to the rest of the system to occur instantaneously.[^atomicity] 18 | 19 | There are two parts here that is interesting. First of all, atomicity is achieved when one or more operations **appear** to happen instantaneously, and also that a synonym for it is **uninterruptible**. Some call this *Zero Downtime Deployment*, like [Envoyer](https://envoyer.io/) does. Let's go back to the teams issue. When deploying, restarting the *php-fpm* service seemed instantaneous to the person deploying, it was a matter of milliseconds. But in this case it proved to interrupt other parts of the system which broke the atomicity of the deploy. 20 | 21 | What we see here is that when an application reaches a critical mass of users and/or a certain complexity, you need to deploy without any interruptions to the service. Striving for your deployment to be fast and responsive just won't cut it; it needs to be atomic. In the previous example, it's not a matter of speed, but of reliability. Even a nanosecond interruption in the *php-fpm* service will result in breaking behaviour for the end user. 22 | 23 | Perhaps you could ask yourself the question, or at least argue for, that it isn't that big of a deal if the user notices or something unintended happens. I could go along with that to a certain extent. However if you're able to solve it, why not keep your users as happy as possible? Interruptions or unintended behaviour could also cause data corruption that you would have to deal with. If we take a look what the other side of the fence might look like, I would say it's quite bad. That would encompass scheduling, notifying users in advance and take your application offline while deploying. In the long run this will most likely not work out. The obvious example is when you need to deploy an urgent hotfix or when the service scales up. 24 | 25 | ## 4.2 Achieving atomicity 26 | 27 | Atomic deploy is when you switch between the previously deployed version and the new one as quick as possible. I'm talking milliseconds or even less, anything slower than that can't be considered atomic. Doing this without your users even noticing is key. Remember the part about making it **appear** instantaneous to the rest of the system? Your application does not need to be very complex or be deployed on an advanced infrastructure, it's enough that somewhere in your process the different parts can come out of sync from each other. Dependencies on packages could get out of sync with your code if the code is updated before dependencies. You also never want to interrupt any current running processes as shown by the example. 28 | 29 | There is a few things that usually need to be in order for achieving atomic deploys and I will go through them below. The next chapter will cover deploy tools and there I'll show you how to do this with the various tools. 30 | 31 | ## 4.3 Concept of builds 32 | 33 | Even though we as PHP developers seldom had to consider the concept of builds of our application it's getting more frequent and we must internalize this for achieving atomic deploys. If we can not create a separate build while deploying, it's not possible. Or actually we could do that with a smart infrastructure, shutting down traffic to servers that are in "deploy mode". But for the sake of argument and simplicity we won't go down that route. There is a simple way of doing this on one server; all we need to do is create a build in a folder that is not being served to our users. Then we can do the old switcheroo on the currently deployed build and the *to be* deployed build without the user noticing. 34 | 35 | Having an appropriate folder structure for allowing this is simple. I will later in this chapter propose a structure, but there are other components to this that I want to discuss prior to it. For now just consider a folder with your application with all dependencies and configuration complete as a build. I would consider a build complete if you can put it anywhere on your server and serve it to your users. 36 | 37 | When switching builds you should also save a number of older builds, perhaps the five or ten previous builds. This allows for quick rollbacks and can be crucial if a deploy needs to be reversed. How many you should save is impossible to answer so just go with a number you feel comfortable with, it also depends on your release cycle. If you deploy once a week or once a day you can probably without any discomfort save your five or ten last builds. If you're deploying continuously on every commit then you should probably save a lot more than five. 38 | 39 | ## 4.4 File persisted data (shared data) 40 | 41 | When switching out a previously deployed build for the new one you must assure that no file persisted data is lost. One example of this is if your application stores session data or user uploaded files inside your application folder. If you switch out the old build for a clean new one, users might be logged out or data could be lost. And this is never an ideal scenario. 42 | 43 | On the other hand there could be file persisted data that you do not care if it gets lost, maybe you would even prefer that it does. If your application has a cache for rendered templates you'd probably prefer if that cache is wiped so your underlying logic and presented views won't get out of sync. In these cases just make sure that your new build replaces or wipes these folders. 44 | 45 | What is important here is to identify the files and/or folders that needs to remain persistent between builds. I refer to these files or folders as **shared data** and I will show how to deal with this in the proposed folder structure. 46 | 47 | ## 4.5 Symbolic folder links 48 | 49 | Although it's not really necessary, but to grok symbolic folder links (*symlinks*) is strongly advised. What we want to do with symlinks is also possible by just copying or moving directories. But to make things atomic we should leverage symlinks since they are pretty much instant. 50 | 51 | A symlink looks like a folder or a file that appear to exist in a location. But the symlink is a reference to a file or a folder in another location. It allows us to instantly switch out which folder or file a symlink is pointing to. When we deploy and want to switch out the old build, we just update a symlink to point to our new build instead. Likewise we will do this for shared data. In that way we make the switch extremely fast and can make sure that our shared data is there when we need it and is stored in one location only. 52 | 53 | For Linux users, it's the [`ln`](http://www.unix.com/man-page/posix/1posix/ln/) command. It's possible on Windows but it's complicated and I suggest doing a Google search for it. 54 | 55 | ## 4.6 Proposed folder structure {#atomic-folder-structure} 56 | 57 | The proposed folder structure needs to reside inside a root directory somewhere. Where that is doesn't really matter, it just needs to contain the following structure. How you develop your application is not of importance and this example assumes that the folder structure is on the server serving the application. 58 | 59 | The names of the folders inside the root are arbitrary, name them as you please. My examples that follow will use this structure and it looks like this: 60 | 61 | ~~~ 62 | ├── builds/ 63 | │ ├── 20141227-095321/ 64 | │ ├── 20141229-151010/ 65 | │ ├── 20150114-160247/ 66 | │ ├── 20150129-083519/ 67 | │ └── 20150129-142832/ 68 | ├── latest/ <- symlink to builds/20150129-142832/ 69 | ├── repository/ 70 | │ ├── composer.lock 71 | │ ├── composer.json 72 | │ ├── index.php 73 | │ └── sessions/ <- symlink to shared/sessions/ 74 | └── shared/ 75 | └── sessions/ 76 | ~~~ 77 | 78 | **builds/** - this folder contains the X number of builds that I discussed earlier. Perhaps the current one deployed and the four previous ones. I suggest naming all builds with a timestamp with date and time down to seconds, you never want a build to overwrite a previous build by accident. 79 | 80 | **latest/** - symlink to the current build that is deployed. Your web server will use this folder as its document root for your site. Unless there is a subfolder that should be served, depending on your set up and framework. 81 | 82 | **repository/** - the folder where the repository resides and this is what we will make builds from. 83 | 84 | **shared/** - any shared data that needs to be persisted between builds will reside here. It can be both files and folders. 85 | 86 | ## 4.7 Pseudo deploy process 87 | 88 | The process for making an atomic deploy is then straight forward: 89 | 90 | * Update the code in *repository/*, probably through pulling the latest changes from your remote. 91 | * Update dependencies and configuration, this is also done in *repository/*. 92 | * Make a copy of *repository/* to a folder in *builds/*. 93 | * Update the symlink *latest/* to point to the new build that was copied. 94 | * Remove excessive builds in *builds/*. 95 | 96 | Need to perform a rollback? Update the symlink *latest/* to point to the previous build you want deployed. 97 | 98 | [^atomicity]: [http://en.wikipedia.org/wiki/Linearizability](http://en.wikipedia.org/wiki/Linearizability) 99 | -------------------------------------------------------------------------------- /manuscript/05-tools.md: -------------------------------------------------------------------------------- 1 | # 5. Tools {#tools} 2 | 3 | So far we have covered theory only but now it is time to get more into the technical aspects of the deployment process. 4 | 5 | In chapter 2 about Goals we talked about the [maturity](#maturity) a deployment process usually goes through. Almost at the bottom (or top, depending on how you see it) of the list was a bullet on **tools**. The beauty is that it will replace or extend steps taken in the previous parts of the maturity process. This is the best place to start if you're setting up a sustainable deployment process for your application. If your current process are in the previous steps of maturity; replacing them with a tool is nothing major in most cases and something you gain a lot from. 6 | 7 | What a tool provides for us is a way of gathering related parts of our deploy in one place. The core principle of a tool is **automation** which we've already covered in both chapter 1 and 2. It also provide a way of making your process readable and you will achieve a kind of self documentation for your process by using any of the tools. It could be considered a manifest for it. 8 | 9 | Most tools do an excellent job in separating your environments. They make it easy to make every deploy to any environment repeatable. Perhaps you don't want to minify your Javascript and CSS files when you deploy to a testing environment? This makes debugging easier. But when you deploy to the staging or production environment you should do it. The tools will help you run the commands you want, when you want and where you want. Some of them also make sure that every person deploying have the correct endpoints to the environments for the deploys. 10 | 11 | This book previously covered many tools , and those were *git hooks*, *Phing* and *Capistrano*. I've made the decision to remove those from the book, since it's my strong opinion that you shouldn't use them. We now have to amazing tools, Deployer [Deployer](#deployer) and [Magallanes][#magallanes] which are written in PHP and made for PHP, so use them. I've also removed *Rocketeer* since it's no longer maintained. If you really want the use the old tools, let me know and I can point you to the appropriate time in the git history for this books' repository. 12 | 13 | {pagebreak} 14 | 15 | ## 5.1 Deployer {#deployer} 16 | 17 | The flow and intuitive interface of [Deployer's](http://deployer.org/) makes me very excited about it. It's written in PHP and is sporting all the good stuff that you'd expect from a modern tool. This includes support for multiple servers and stages, deployment in parallel, atomic deployment and on top of that: it's fast. What I really enjoy about it is the fluent way you define your process in it, it provides a smooth and enjoyable experience. And in some cases everything works out of the box with their *recipes* available for a few frameworks and general use cases. 18 | 19 | One could argue that the simplicity of the tool is what might make it suffer in some cases. It revolves around one file, a `deploy.php` file in your project's root directory. You could include other files in that, making your own folder structure for your process; and this could be seen as both a plus or a minus. It provides you with flexibility, but leaves a lack of structure and common practices. 20 | 21 | It has been around for a few years and have reached maturity and stability. As of writing this the maintainers are actively working on the tool and updating it on a regular basis, it also have a solid contributor foundation. 22 | 23 | If you or your company is using Deployer, please consider to help out the developer by [becoming a sponsor](https://github.com/sponsors/antonmedv). 24 | 25 | ### Installation 26 | 27 | Install it in your project by requiring the package. At the time of writing this the latest version is `6.8.0` so that is the one I'll be using. 28 | 29 | {lang=bash} 30 | ~~~ 31 | composer require deployer/deployer:6.8.0 --dev 32 | ~~~ 33 | 34 | Then if you have your PATH configured correctly you can run the binary through: 35 | 36 | {lang=bash} 37 | ~~~ 38 | dep 39 | ~~~ 40 | 41 | ### Configuring 42 | 43 | Start off by initializing it for your project, do this in the root folder of the project. 44 | 45 | {lang=bash} 46 | ~~~ 47 | dep init 48 | ~~~ 49 | 50 | You will be prompted if you want to use one of the recipes provided by the tool, choose one if applicable or go with the *Common* one. I went with the *Common* recipe since I'll be deploying my [deploy test application](https://github.com/modess/deploy-test-application) which has some dependencies and a storage folder. You'll also be prompted for the location of the repository you wish to deploy, in my case `git@github.com:modess/deploy-test-application.git` which it automatically detected. Then you choose if you want to send anonymous statistics to help and aid the developers of the tool, if you don't have sensitive things you're deploying I suggest you answer *yes* here. Sharing is caring. 51 | 52 | A `deploy.php` have now been generated in the root of your project, take a quick look at it. You can already tell the fluent and nice interface the tool have for structuring your deployment process. This is also one of the beautiful things with the tool, a single file is generated and that is all you need for now. When your application grows you might need to extends this and then you're able to do that. 53 | 54 | I> ### Web server document root 55 | I> 56 | I> Deployer uses **current** as a symbolic link to the current deploy, we need our web server to use that as the document root. 57 | 58 | #### Hosts 59 | 60 | I'll start my cleaning up the host(s) I want to deploy to. I'll only have one since this is a simple test deployment environment, in a real world scenario you'll probably have multiple ones for staging, production, and so on. 61 | 62 | {lang=php} 63 | ~~~ 64 | leanpub-start-delete 65 | host('project.com') 66 | ->set('deploy_path', '~/{{application}}'); 67 | leanpub-end-delete 68 | leanpub-start-insert 69 | host('my-app.com') 70 | ->set('deploy_path', '/var/www/my-app.com'); 71 | leanpub-end-insert 72 | ~~~ 73 | 74 | #### Shared and writable files/folders 75 | 76 | We need to make sure that we have our `storage` folder. Setting up shared and writable folders and files comes right out of the box with Deployer, take a look at these configuration settings. 77 | 78 | {lang=php} 79 | ~~~ 80 | set('shared_files', []); 81 | set('shared_dirs', []); 82 | set('writable_dirs', []); 83 | ~~~ 84 | 85 | We'll update this to: 86 | 87 | {lang=php} 88 | ~~~ 89 | set('shared_dirs', ['storage']); 90 | set('writable_dirs', ['storage']); 91 | ~~~ 92 | 93 | #### Restarting services 94 | 95 | Then we have a task for restarting the PHP-FPM service, and in case you're running *nginx* as your web server this is something that should be done. I however don't suggest you restart it, but reload it. Depending on the linux distribution you're running on your server(s) you'll have to change this accordingly. This what I'm using: 96 | 97 | {lang=php} 98 | ~~~ 99 | leanpub-start-insert 100 | task('php-fpm:restart', function () { 101 | run('sudo service php-fpm reload'); 102 | }); 103 | after('deploy:symlink', 'php-fpm:restart'); 104 | leanpub-end-insert 105 | ~~~ 106 | 107 | ### Deploying 108 | 109 | That's all! We can now deploy the application by running 110 | 111 | ~~~ 112 | dep deploy 113 | ~~~ 114 | 115 | A nice and simple list of tasks being executed by the tool is displayed. 116 | 117 | ### Tasks 118 | 119 | Deployer comes with a set of predefined recipes as they're called, which consists of _tasks_. A task is a step performed in the deployment process necessary for your application to work properly once deployed. This can range from simple one line bash commands such as `php artisan migrate` to more advanced steps such as telling your load balancer that "I'm deploying, leave me out of rotation for now" and then telling it that you're back. 120 | 121 | When I initiated my project the recipe I used generated the following tasks for deploying: 122 | 123 | {lang=php} 124 | ~~~ 125 | desc('Deploy your project'); 126 | task('deploy', [ 127 | 'deploy:info', 128 | 'deploy:prepare', 129 | 'deploy:lock', 130 | 'deploy:release', 131 | 'deploy:update_code', 132 | 'deploy:shared', 133 | 'deploy:writable', 134 | 'deploy:vendors', 135 | 'deploy:clear_paths', 136 | 'deploy:symlink', 137 | 'deploy:unlock', 138 | 'cleanup', 139 | 'success' 140 | ]); 141 | ~~~ 142 | 143 | All of these come out of the box with the tool because they are general tasks for deploying a PHP application. If you write your own tasks you can either add your task to this list where you see fit or use a hook, as can be seen in the task for restarting PHP-FPM: 144 | 145 | {lang=php} 146 | ~~~ 147 | task('php-fpm:restart', function () { 148 | run('sudo service php-fpm reload'); 149 | }); 150 | after('deploy:symlink', 'php-fpm:restart'); 151 | ~~~ 152 | 153 | Here we tell it that after the `deploy:symlink` task is executed, execute our task `php-fpm:restart`. 154 | 155 | When your application have special requirements you can easily write your own tasks and make sure they are executed just where you need them. If needed you can also start splitting up your `deploy.php` into more parts, having one monolithic file might not be good if it grows too much. 156 | 157 | {pagebreak} 158 | 159 | ## 5.2 Magallanes {#magallanes} 160 | 161 | -------------------------------------------------------------------------------- /manuscript/06-version-control.md: -------------------------------------------------------------------------------- 1 | # 6. Version control {#chapter-version-control} 2 | 3 | This is not a book about version control, neither will this chapter go into how and why you should version control. This chapter will discuss a workflow that is suitable for optimizing your deployment process. You do this through a *branching strategy*. 4 | 5 | As said before, Git will be used as a reference in this chapter. But the concepts can probably be applied to most version control systems if you're able to create branches. Why Git is so beneficial in a branching strategy is because of its low overhead for those operations. Branching and merging is mostly fast and simple. Conflicts will arise in all systems. 6 | 7 | The overhead in Git when you create a new branch is minimal, we're talking around **4 kilobytes**! And it only takes a few milliseconds of your life. I'm pretty sure no other major version control system can contest that. If you are interested in how this is possible, I'll provide some resources for further learning on Git. 8 | 9 | And why is version control important for your deployment process? You might ask. It comes down to being able to get code to its proper places. With a good version control system and a workflow, you can choose what you push code to the different environments. And for this you use a branching strategy. I will propose one in this chapter and I'm not saying that's the one you have to use. There are many strategies out there and you should try to find one that fits you and your team. 10 | 11 | The term branching strategy is just a fancy way of explaining when, how and where you branch from and merge to. Think of it as a schematic for your code flow between branches and environments. 12 | 13 | ## 6.1 Git-flow 14 | 15 | I'm proposing the git-flow branching strategy that is widely used and have a proven record. It probably is the most used branching strategy for Git. It started with a guy named Vincent Driessen who published a blog article called [A successful Git branching model](http://nvie.com/posts/a-successful-git-branching-model/). He wanted to share his workflow he had introduced for both private and work related projects. Maybe he wasn't the first, but his article is used as the main reference for git-flow. It has since then been praised and adopted by many people who work with software development. Many companies and open source projects have introduced it as their branching strategy. 16 | 17 | There is nothing magical about it though, no unicorns or any of that shit. It's just a branching **strategy**. You can work according to it just using the git binary, or you can use a [git extension](https://github.com/nvie/gitflow) for it. But all in all it's a way of which branches you have, how you name them and how you merge them. 18 | 19 | Some tools have adopted it too. One of the most popular Git clients, [SourceTree](http://www.sourcetreeapp.com/) by Atlassian, have support for it out of the box. You don't even have to install git-flow on your system to work with it in the application. It can convert a current repository to support the workflow and then it helps you with all the steps. I recommend you use a Git client. It makes things a lot simpler and you get a better overview of your repository. Only when I need to do more advanced operations like going through the reflog or such, I will resort to the command line. 20 | 21 | ## 6.2 Main branches 22 | 23 | There are two branches that have to exist in order for git-flow to work. These are your main branches and serves two different purposes. They are called *master* and *develop*. When converting a repository for git-flow you will end up with these branches. You can name them whatever you want, but I will refer to them as their original names. 24 | 25 | ### Master branch 26 | 27 | This is the branch you have in your production environment. It should always be stable and deployable. I'm trying to come up with some other things to say about it, but there is nothing more to it actually. The code here is the face of your application that its users see. 28 | 29 | ### Develop branch 30 | 31 | Your develop branch is where all deployable code should be merged to. This is a branch for testing your code before it gets merged into *master*. Once code is in the *develop* it can be pushed to different environments so it can be tested thoroughly. Perhaps you have a QA-person/team that can test your features with manual and/or automated regression tests. But it should also pass some sort of automated testing (continuos integration) like Jenkins or Travis. And your fellow developer colleagues could test it in a shared environment. 32 | 33 | The code here should be stable enough to be merged in to master at any point. But of course bugs will be spotted and dealt with and that's the whole point of this branch. You want your code as tested as possible so you can merge it into master and deploy it into production. You want to be able to do this with as little uncertainty as possible. 34 | 35 | ## 6.3 Feature branches 36 | 37 | All features you are working on should have its own branch. These branches are prefixed with *feature/*. If for example you're working on a OAuth implementation, it be called *feature/oauth-login*. How you name the branches after the prefix is completely up to you though. If you use a service for issue tracking it can be good to have the issue number in the name as well for traceability. 38 | 39 | They all start out from develop, and up there as they are done. So a feature branch will branch out from develop and when the feature is complete it goes back there. This is true for all feature branches. 40 | 41 | ## 6.4 Release branches 42 | 43 | Before a deploy you will create a release branch. It branches off from *develop* and will get a *release/* prefix. Depending on how and what you deploy, the name will differ. If you're working in sprints it could be the sprint number, eg. *release/sprint-39*. 44 | 45 | When you have your release branch it can be pushed to the different environments and tested. One of the most important places to do this is in your staging environment. You want to make sure it works in an as close to production environment as possible. Because when it has been tested it will be deployed. 46 | 47 | So you have a release branch you want to deploy, then what? Then it will be merged into **both** develop and master. It will also be tagged with an appropriate tag, such as a version number. This ensures that all your deploys correspond to a tag. It's an important concept when you want to roll back, because you then want to roll back to the previously deployed tag. 48 | 49 | ## 6.5 Hotfix branches 50 | 51 | These are the branches you hope to never use. But you will. Why? Because these are the branches you use when something went wrong and needs to be fixed asap. If you discover some nasty bug in production that requires a quick response, a hotfix branch is your go to guy. Can you guess the prefix for it? Yes, it is *hotfix/*. 52 | 53 | When you create one it will branch off from *master*. Since you might already have new changes in *develop*, you do not want those to end up in production yet. You will then fix your bug in this branch. If you have time this should be tested as much as possible too, but sometimes you have to deploy it ten minutes ago. 54 | 55 | They are treated just as release branches when merging. Shove it into *develop* and *master*, create a tag and off it goes to production. 56 | 57 | ## 6.6 Labs branches 58 | 59 | This is outside the scope of git-flow and is only a personal preference I have. Often I end up with things I want to play around with that is not tied to actual deliverables. Then I want to separate my playground from my work stuff, so I prefix them with *labs/*. Often these branches will be played around with and then thrown away. If they do end up being complete I merge them in to *develop* like a regular feature branch. 60 | -------------------------------------------------------------------------------- /manuscript/07-dependencies.md: -------------------------------------------------------------------------------- 1 | # 7. Dependencies 2 | 3 | The day has long passed since dependencies in a PHP application was not managed by one or more package managers. Composer made it easy for people to share their packages with the world and let other people to use them, and also contribute to them. It has in a way brought the members of the PHP community closer together. One might of course ask the question on why dependencies is even brought up in a book about deploying applications. The answer is that managing, updating and installing dependencies in a consistent way so they stay synchronized between all environments can sometimes be tricky. 4 | 5 | Any modern application have (or at least should have) some dependencies. This does not have to be PHP specific, usually it's a mix of dependencies from one or more dependency management tools. The most popular being Composer, Node Package Manager (NPM) and Bower. I probably don't have to tell you about Composer, it's the de-facto standard for PHP packages your application depend on. NPM is a versatile tool that can be used to install dependencies for tools and both frontend and backend packages. Since *node.js* can be used for both server and client side code, its package manager reflects this. And it's often used for build tools and their dependencies, such as *grunt* or *gulp*. Then there is also Bower which is great at handling dependencies for the frontend, pretty much any assets you can think of can be managed with it. 6 | 7 | In this chapter I will not describe how to use these tools; instead I'm going to discuss what they have in common. What they have in common is the way we specify versions for our dependencies, why that is and how we effectively can manage our dependencies when understanding this. They all follow a standard called **semantic versioning**. 8 | 9 | ## 7.1 Semantic Versioning 10 | 11 | One day Tom Preston-Werner, one of the co-founders of github, decided it was time for a proposal on how software versioning should be done. The result was [Semantic Versioning](http://semver.org/) and it has since been widely adopted by developers and tools. Understanding the principles of semantic versioning will make your life managing dependencies easier. The ongoing development is of course [available on github](https://github.com/mojombo/semver). 12 | 13 | ### Specification 14 | 15 | The foundation is extremely simple, a version consists of three parts. The *MAJOR*, *MINOR* and *PATCH* parts, and they **must** exists on all versions. The format of this is `X.Y.Z` in the order I previously stated, this is how I will continue to mention them. `X` for a major version, `Y` for a minor version and `Z` for a patch. If a version is missing any of these parts, it can not be considered to adhere to semantic versioning. I will sometimes write certain parts in lower case, when I do this it's to highlight something of importance for one of the other parts which will be in upper case. 16 | 17 | For a quick example: a major release could be `1.0.0`, when a minor version is released it will become `1.1.0`, and if it is patched it will become `1.1.1`. The version can also be suffixed with `-dev`, `-patch`, `-alpha` or `-beta`, such as `1.0.0-RC1`, but I will not go deeper in how the version constraints deal with these edge cases. 18 | 19 | The list of how versioning must be applied to comply with semantic versioning is long, but the interesting parts are: 20 | 21 | * Once a version has been released, it must not change under any circumstances. 22 | * Major version zero (`0.y.z`) is for initial development only, **anything can change at any time**. 23 | * Major version one (`1.0.0`) must declare a *public API*, this can be in form of explicit documentation or by the code itself. 24 | * Once a public API has been declared, all non-backwards compatible changes must increase the major version (`1.y.z` would become `2.0.0`). 25 | * Minor version (`x.Y.z`) must be incremented if new backwards compatible changes have been made to the public API or if marks anything as deprecated in it. There are also some cases when you may increase it. 26 | * Patch version (`x.y.Z`) must be incremented only if backwards compatible bug fixes are introduced. 27 | 28 | Okay, a lot of variables here, but it serves us great purpose to understand this. With this in mind we can define versions for our dependencies with understanding and predicting their update behaviours. Since semantic versioning supports wildcards and constraints, we can more easily predict the way our dependencies will be updated. It can also aid us in selecting dependencies. If we need a package that will be used in a crucial part of the system, we should almost never use a package that does not have a `1.0.0` release; since no public API has been defined and anything can change at any time. This includes breaking changes that could break your application. 29 | 30 | Let's say you want to use the popular HTTP client package Guzzle. You can now surely tell the difference between including version `5.2.*`, `5.*.*` and `*` of this package. The last example is a terrible idea, since it will install *all* new versions of the package, including when `6.0.0` will be released. Since that would be a major version, which by definition includes non-backwards compatible changes, it will most likely break your application. Perhaps `5.*.*` will work because it's supposed to include only backwards compatible changes, but could you live with peace of mind hoping that the maintainer of the package won't introduce breaking changes? 31 | 32 | Remember, package maintainers are people too and are not fault free. They do all their hard work in trying to help you with their code; but shit happens and you should probably cut them some slack when it happens. The number of possible versions your version constraint accepts will probably correlate to the possibility of your application receiving breaking changes. This results in a balancing act; stability on one side and potential security flaws and bugs on the other side. Finding that balance is not an easy formula; just as many other things it depends on a number of variables. 33 | 34 | ### Version constraints 35 | 36 | We now have the background for the versioning of the dependencies we might use; but there are some special operators we can use when defining our version constraints. 37 | 38 | If you've ever used Composer, npm or Bower, you have seen package definitions in the corresponding file the package manager uses. It can look like this: 39 | 40 | ~~~ 41 | "some-package": "1.0.0" 42 | ~~~ 43 | 44 | This is the most simple example of defining a version constraint to a package, whatever you do it will always install version `1.0.0` of this package. If you would like to include all new patch versions for this major version, you could change it to: 45 | 46 | ~~~ 47 | "some-package": "1.0.*" 48 | ~~~ 49 | 50 | If the package maintainer is doing a good job, this should never be an issue and I always prefer this. Since it could fix bugs or security exploits you haven't encountered yet, and by definition is should never include any breaking changes. Trust the maintainer even more? Change it to: 51 | 52 | ~~~ 53 | "some-package": "1.*" 54 | ~~~ 55 | 56 | There is hardly any merit in taking this approach. What you're accepting now is added functionality and deprecations to the public API, but no non-backwards compatible changes. If you've built your code depending on `1.1.0` for example, you won't automatically leverage changes in `1.2.0` because it's added functionality. So why risk it? Perhaps one reason might be possible performance improvements, but it's probably still not work the risk. I would argue that even dependencies that are for development only should not take this approach, because it could break a build step or test suite somewhere and stop your deploy or development process. Upgrading a minor or major version should always be an active effort from a developer, making sure things are working. 57 | 58 | #### Ranges 59 | 60 | Version constraints can also be specified using ranges, the valid ones are 61 | 62 | * greater than: `>` 63 | * greater than or equal to: `>=` 64 | * less than: `<` 65 | * less than or equal to: `<=` 66 | * not equal to: `!=` 67 | * between: `-` 68 | 69 | How these work should come pretty easy for a developer, but they also support logical *AND* and logical *OR*. A space or a comma will be treated as a logical *AND*. So `>1.0 <1.1` or `>1.0,<1.1` is the same expression and can be pronounced *greater than 1.0 and less than 1.1*. To make a logical *OR* you use `||`, that could look like `<1.0 || >1.1` which can be pronounced *less than 1.0 or greater than 1.1`. 70 | 71 | I> ### Logical operators precedence 72 | I> 73 | I> A logical *AND* will always have precedence over logical *OR*. 74 | 75 | You can also make a range by using a hyphen, with it you can specify a range between two versions such as `1.0 - 2.0`; which should be self explanatory. 76 | 77 | #### Tilde 78 | 79 | This is one of two special version constraint operators you will encounter. A tilde translated into english could be: *install this version and upgrade the packages lowest version constraint specified, but never anything higher (and also never a major version)*. Let's look at some examples. 80 | 81 | | Will upgrade to: | 2.0.2 | 2.0.3 | 2.0.4 | 2.1.0 | 82 | | ------------------------ | ----- | ----- | ----- | ----- | 83 | | "some-package": "~2.0.1" | Yes | Yes | Yes | No | 84 | 85 | The lowest constraint specified here is the patch version. It will never install any version lower than `2.0.1` (such as `2.0.0`) and it will upgrade to any `2.0.Z` version. But it will **not** upgrade the minor version. 86 | 87 | Another example of the tilde operator: 88 | 89 | | Will upgrade to: | 2.1.1 | 2.1.2 | 2.2.0 | 2.2.1 | 3.0.0 | 90 | | ---------------------- | ----- | ----- | ----- | ----- | ----- | 91 | | "some-package": "~2.1" | Yes | Yes | Yes | Yes | No | 92 | 93 | This time our lowest constraint specified is minor version. It will never install any version lower than `2.1.0` (such as `2.0.0` or `2.0.8`) and it will upgrade to any `2.Y.Z` version. But it will **not** upgrade the major version. 94 | 95 | And the final example: 96 | 97 | | Will upgrade to: | 2.0.1 | 2.0.2 | 2.1.0 | 2.1.1 | 3.0.0 | 98 | | -------------------- | ----- | ----- | ----- | ----- | ----- | 99 | | "some-package": "~2" | Yes | Yes | Yes | Yes | No | 100 | 101 | Notice something familiar with this? It behaves the same as `~2.1`, the difference is that it can install lower versions than `2.1.0`. 102 | 103 | #### Caret 104 | 105 | This is the other kind of special operator for version constraint, the caret `^`. It behaves slightly different than the tilde operator does, being more or less conservative in certain scenarios. We'll run through examples for this as well: 106 | 107 | | Will upgrade to: | 2.0.2 | 2.0.3 | 2.0.4 | 2.1.0 | 2.2.1 | 3.0.0 | 108 | | ------------------------ | ----- | ----- | ----- | ----- | ----- | ----- | 109 | | "some-package": "^2.0.1" | Yes | Yes | Yes | Yes | Yes | No | 110 | 111 | This is a very liberal constraint. You tell it to install `2.0.1` and then upgrade to **any higher version that is not a major version**. 112 | 113 | On we go: 114 | 115 | | Will upgrade to: | 2.1.1 | 2.1.2 | 2.2.0 | 2.2.1 | 3.0.0 | 116 | | ---------------------- | ----- | ----- | ----- | ----- | ----- | 117 | | "some-package": "^2.1" | Yes | Yes | Yes | Yes | No | 118 | 119 | This is the exact same as `~2.1`, but there is a big difference when dealing with packages that doesn't have a major version yet. Let's do the same thing but with `^0.2`: 120 | 121 | | Will upgrade to: | 0.2.1 | 0.2.2 | 0.2.3 | 0.3.0 | 122 | | ---------------------- | ----- | ----- | ----- | ----- | 123 | | "some-package": "^0.2" | Yes | Yes | Yes | No | 124 | 125 | If we had used `~0.2` here instead, it would have allowed anything lower than `1.0.0`. But using the caret on packages without a major release (no public API defined), we can handle these more conservatively. This can be very effective when we want to depend on packages without a major release and want to protect ourself against breaking changes as much as possible. 126 | 127 | Using `^2` is the same as `~2` and `2.*`. 128 | 129 | ### Game of balance 130 | 131 | Dealing with version constraints is always a game of balance and the same rules will not apply everywhere. Your applications critical parts should probably never depend on a package with a wide range, but instead be narrow. 132 | 133 | Let's take a real world example of this that happened to one of the most popular PHP frameworks, Laravel. A security exploit was discovered in version `4.1.25` that needed an urgent fix. The security exploit allowed for hijacking of "remember me"-cookies used in authentication; this allowed the attacker to remain logged in as another user for a very long time. A patch was written and `4.1.26` was released. What was less known to the community was that this patch introduced a non-backwards compatible change. Anyone who had `4.*` or `4.1.*` for example that updated their dependencies would receive the change which broke their entire application. This caused quite an outrage and people were extremely upset, however the intent of the patch was good and it needed a change to the database schema. The people who had `4.1.25` specified as their version constraint could sit back and watch. They could then pull down the latest version in their development environment, update their code and database schema to align with the patch. When they were done they could easily deploy their application when it was working as intended with the patch. This was also true for people using Composer's lock file (more on that later in the chapter). 134 | 135 | This is a great example since it was a dependency that many applications relied on, the ultimate core dependency (if you do not count Laravels dependencies). This dependency were mission critical for all those applications and people lived in good faith of that a patch version should never introduce a breaking change. But it happens, and sometimes must happen. The alternative would be to leave the framework unpatched unless upgrading to a major version, since that is what semantic versioning constitutes. That is not really an option, and suddenly releasing a new major version just for this little security fix would be very troublesome for the maintainer and confusing for the users. 136 | 137 | You will have less mission critical parts of your system where you can be more liberal in version constraints. If you have filterable lists in your application that allows your users to export them to spreadsheets; your dependency for generating Excel files probably won't be mission critical. Then you can open up your constraints a bit to allow for bug, security and performance fixes without you having to worry about it. 138 | 139 | ## 7.2 Version control 140 | 141 | The most frequent question I hear when it comes to dependencies is "should I version control them?" or "should they be committed to the repository?". Short answer: **no, never**. Some people will argue that you should version control them with arguments such as you know they will work with the current code or that Github might be down when you need to install or upgrade them. If you know how to manage your dependencies with version constraints, the first issue will never arise. The second issue could arise, and I've been there a few times myself. However it happens so seldom that I consider it to be a problem that can be ignored. If it occurs at the exact same moment you need to deploy an urgent hotfix, well that sucks. But perhaps you then could instead do a rollback to your previous build? 142 | 143 | Having dependencies in your version control gets really messy. For one they increase the size of your repository, most often they will be a larger part of your repository than your application. Second they will be part of commits and pull requests, increasing the entropy in both. Pull request should be short and sweet, having an entire package in there will only bloat it. If you instead just change one line in a package managers dependency file, it will be abundantly clear which package and what version of it you're using. 144 | 145 | The only exception is when you version control **entire builds** of your application. This could be common when deploying Docker containers or such. You could make it a part of your normal deploy process as well, if you want to save every build of your application. But a git tag for all your build points should suffice. 146 | 147 | ## 7.3 Development dependencies 148 | 149 | Almost any dependency management tool separates packages in (at least) regular dependencies and development dependencies. This allows for a good separation in your workflow when dealing with dependencies in certain environments. Take *phpunit* for example, this is a package you want to have in your local environment if you need to run unit tests for your PHP code. Perhaps you do continuous integration, then you need it there too to be able to run your test suite. But once your application is deployed and served to your end users, there is no need for this package to be installed. You would never run a test suite in your production environment. 150 | 151 | It's because of these scenarios that the dependency management tool separate packages in this manner. Having all your dependencies installed in your production environment will slow down the install/upgrade process for them and it could potentially slow down your application as well. Make sure you separate them appropriately; there is one really simple question when installing a package, and that is "Will I ever need this in production?". If the answer is no, always put it amongst your development dependencies. And never install those dependencies in your production environment. 152 | 153 | The default behaviour for the tools is always to install development dependencies, so make sure you turn that behaviour off in your production environments. Here are the flags to use for the different tools when installing or updating without installing the development dependencies: 154 | 155 | | Tool | Flag | 156 | | -------- | ------------ | 157 | | composer | --no-dev | 158 | | npm | --production | 159 | | bower | --production | 160 | 161 | ## 7.4 The composer.lock file 162 | 163 | Whenever you do an install or update of your dependencies through Composer, a `composer.lock` file will be generated along side your `composer.json` file. There have been some discussion on the lock file in the community. Usually it's people making an effort to inform others on what the lock file provides and how it makes Composer behave when running *install* or *update*. So should you commit it to your repository and version control it? The answer is in most cases **yes**. Since this book is about *applications*, the answer should be yes. If you're instead developing a component or a library, you should turn to the interwebs for further advice. 164 | 165 | So what does this file actually achieve? Except from generating merge conflicts in your repository? **It registers exact versions of the installed dependencies**. If you install version `1.2.3` of a package, commit the lock file and push your changes; when your co-coder pulls down your changes to the lock file and runs `composer install`, she will get version `1.2.3` even if the version constraint is `1.2.*` and `1.2.7` is the latest patch version. 166 | 167 | So you should get into the habit of committing the lock file to any repository for your applications. And also try to use `composer install` instead of `composer update` unless you willingly want to update packages. One added benefit to having your lock file version controlled is also that installing dependencies will be faster since Composer does not have to do version discovery (see next section). 168 | 169 | I recommend you open a lock file in one of your projects and take a look. It's a json representation of your packages and quite readable to humans. 170 | 171 | W> ### Updating all dependencies 172 | W> 173 | W> When you run `composer update`, all dependencies will be updated. Beware that this could affect parts of the code you're not currently working on. If you want to only update a specific package, use `composer update vendor/package` instead. 174 | 175 | ## 7.5 Composer and github rate limit 176 | 177 | You should keep in mind that Composer uses the Github API for version discovery and downloading dependencies. The API enforces a rate limit which means that you're only allowed a certain amount of requests in a certain amount of time. That could easily get chewed up when installing or updating dependencies through Composer. The big issue with this is that in an automated deploy process it will often halt the install/update of dependencies and ask for your login credentials for github. Logged in users does not have the same rate limit as a guest user and it allows for you to install everything without any problems. But you do not want this prompt in your process because it will stop everything. However there are things you can do to prevent it. 178 | 179 | Committing the `composer.lock` file previously mentioned is a good practice, because it tells Composer exactly what version to install. That removes version discovery from the process. If you have `2.0.*` as a version constraint without a the lock file committed, Composer has to query the API for finding the suitable version to install. The lock file will remove the need for it. 180 | 181 | The other thing you can do is to [generate an OAuth token](https://help.github.com/articles/creating-an-access-token-for-command-line-use/) for your Github account and add it to `~/.composer/config.json` in this format: 182 | 183 | {lang=json} 184 | ~~~ 185 | { 186 | "github-oauth": { 187 | "github.com": "your_github_oauth_token" 188 | } 189 | } 190 | ~~~ 191 | 192 | Composer will then use that OAuth token when interacting with the Github API and the strict limits will be lifted. 193 | 194 | I> ### Entering credentials when prompted 195 | I> 196 | I> If you end up doing a manual install or update and get prompted for credentials, entering them will generate an OAuth token that will be entered in `~/.composer/config.json` for future use. 197 | -------------------------------------------------------------------------------- /manuscript/08-database-migrations.md: -------------------------------------------------------------------------------- 1 | # 8. Database migrations 2 | 3 | Almost all applications that need to persist data will end up using a database of some sort and in most cases MySQL or PostgreSQL. With this comes a database schema (or structure if you'd like) of tables and columns that is necessary for the application to store data it uses for certain tasks such as state or statistics. The process of dealing with changes to the database schema has often been dealt with manually where in best cases some sort of documentation or sequenced SQL files ending up in a repository for reference. 4 | 5 | But now we have entered a new era of PHP application development and the tools for dealing with database changes are now modern and often built into frameworks. The most used term is *migrations* and in this chapter I will use the [Phinx](https://phinx.org/) as the weapon of choice for handling migrations. 6 | 7 | The benefits are many and the two major ones are that you get versions of your database schema where each step either create, delete or manipulate tables or columns in the previous schema. You will get, if you don't manually change it, a chronological list of files that contains your database changes. Adding a new migration file along with your feature or fix you can make sure that the database gets updated or performs a rollback along with the code automatically, once you add this to your deploy process. Which migration files to run next or to rollback is stored in the database itself. There is also an added benefit of getting developers up and running when starting to develop on your application, you do not need to dump your database and import it or keep track of the structure in a separate file. Clone the repository and run the migrations and they should have a fully functional database to work with. 8 | 9 | ## 8.1 Installation & configuration 10 | 11 | As always I recommend using Composer to install Phinx 12 | 13 | composer require robmorgan/phinx 14 | 15 | This will install all dependencies and the Phinx binary at `vendor/bin/phinx`. So let's bootstrap a configuration file: 16 | 17 | php vendor/bin/phinx init 18 | 19 | You can now find `phinx.yml` in the root directory of your project. Take a look inside it and set up your database credentials. 20 | 21 | ## 8.2 Migration files 22 | 23 | Generating migration files is really easy, you run the command `php vendor/bin/phinx create NameOfYourMigration` and it will generate a file named `YYYYMMDDHHMMSS_name_of_your_migration.php` in the `migrations/` folder unless you change this in the configuration file. 24 | 25 | All migration will end up in files that extends `Phinx\Migration\AbstractMigration` and will initially consist of a `change()` method only. This is a behavior that was introduced in version *0.2.0* of Sphinx and I do not like it. The reason being that it introduces magic in our migrations by trying to guess how to revert changes and leaves you with no option of modifying that, also it only works with a certain set of operations. I prefer the old fashioned way of using `up()` and `down()` since it enables us to be flexible and I will in the next section continue my reasoning on this. So I always start with replacing the contents of my newly generated migration file with: 26 | 27 | {lang="php"} 28 | ~~~ 29 | public function up() 30 | { 31 | // Up changes goes here 32 | } 33 | 34 | public function down() 35 | { 36 | // Down changes goes here 37 | } 38 | ~~~ 39 | 40 | These two methods is where you put your intended changes to the database. `up()` will be the method for when you migrate and the `down()` method is used for reverting the changes you made in the `up()` method, this is to provide consistency in migrating and performing rollbacks. So in a case of creating a table for users when migrating, you would drop that table when performing a rollback. 41 | 42 | We can now start to adding our changes to the migration file. Let's create that user table I've been going on and on about, it would look like this. 43 | 44 | {lang=php,crop-start-line=7,crop-end-line=21} 45 | <<(code/chapter-8/migrations/20150817080530_create_users_table.php) 46 | 47 | A simple table with names `users` than contains string columns for e-mail and password that can't be null. It also adds a unique index for the e-mail column since our database should never be able to store multiple users with the same e-mail. When we revert this change we simply drop the table. 48 | 49 | The valid column types that you can pass to `addColumn()` are: 50 | 51 | * string 52 | * text 53 | * integer 54 | * biginteger 55 | * float 56 | * decimal 57 | * datetime 58 | * timestamp 59 | * time 60 | * date 61 | * binary 62 | * boolean 63 | * enum (MySQL only) 64 | * set (MySQL only) 65 | * json (Postgres only) 66 | * uuid (Postgres only) 67 | 68 | Along side these you can apply options to the column, such as we did with `null`, and they can be found in the Phinx documentation [here](http://docs.phinx.org/en/latest/migrations.html#valid-column-options). 69 | 70 | At this point we realize we want to keep track of when each user was created, so we need a column for that in the user table. We first generate a new migration file: 71 | 72 | php vendor/bin/phinx create AddCreatedAtToUsersTable 73 | 74 | We can now select our `users` table again and apply the changes we want and also provide a revert where we simply drop the column. 75 | 76 | {lang=php,crop-start-line=7,crop-end-line=21} 77 | <<(code/chapter-8/migrations/20150818081646_add_created_at_to_users_table.php) 78 | 79 | ## 8.3 Possible data loss on rollbacks 80 | 81 | Something important to bear in mind when reverting changes is that you do not want to accidentally lose vital information. Say you implement a feature for users to pay for a subscription of some sort in your application and you create a column for storing a timestamp when their subscription expires. If you end up having to rollback this feature after a couple of days and your migration files just simply delete this column you could end up in a situation where you don't know how long a user should have their subscription. 82 | 83 | To prevent this you can in your migration scripts either rename columns or tables instead of deleting them or you can copy the data to a temporary table or column. When the rollback is performed you can then manage the data manually since it will be outside of your automated changes to the table or column. More than often I do not encourage doing things manually but this is one occurrence where it really can not be dealt with in an automated way. 84 | 85 | ## 8.4 Batches 86 | 87 | Your application will most likely not be deployed feature by feature, most likely it will be deployed release by release where each release contains one or more features. So a release could consist of more than one migration file, even a single feature could contain more than one. When you deploy and your deploy process automatically applies these changed it will take all the migration files that wasn't included in your latest release and run them against your database. All and well until you want to do a rollback when your latest deploy had multiple migration files because now it will only take the latest migration file and revert the changes in those. The chain of automation is now broken and we need a way of tracking the batches of migration files. 88 | 89 | This example illustrates the problem: 90 | 91 | ``` 92 | ├── Release #1 93 | │ 20150817080530_create_users_table.php 94 | │ 20150818081646_add_created_at_to_users_table.php 95 | ├── Release #2 96 | │ 20150831053347_add_username_to_users_table.php 97 | │ 20150831053404_add_unique_username_index_on_users_table.php 98 | └── 99 | ``` 100 | 101 | After deploying our two releases we perform a rollback, our database will then be at the state of *20150831053347_add_username_to_users_table.php* since only the latest migration will be reverted. This puts our application and database out of sync unless someone manually performs another rollback on the database (this could also mean performing multiple rollbacks, one for each change). So we need a way of tracking our migrations as batches so we can pinpoint where we want our rollback to revert to. Unfortunately this is not something that Phinx provides for us and they have [a reason](https://github.com/robmorgan/phinx/issues/99) for it. 102 | 103 | I have not found any good tools for dealing with these situations outside of frameworks. *Laravel* for example solves this by tracking the batch number inside its migrations database table and performs migrations and rollbacks based on that. What you need is a persistent storage for the latest migration version of each batch which can be in a file, in a database or in a cache layer. If you put it in a cache layer you need to make sure your cache layer is persistent and not only an in-memory cache. I've created a very simple class for dealing with persisting the batches to a file, nothing fancy and very prone to errors but its only purpose is to show how it can be dealt with. For example it can't accept any parameters to be passed to Phinx, it doesn't deal with any errors that might occur but instead it will only perform the necessary actions if the Phinx binary responds with an exit status of 0, which in Linux language means that everything is okay. You can take a look at the example, but *I urge you never to use it in production*. 104 | -------------------------------------------------------------------------------- /manuscript/09-running-tests.md: -------------------------------------------------------------------------------- 1 | # 9. Running tests 2 | 3 | We can (almost) all agree on that tests are good for software and they should be written to some extent. How large your test suite is and how specific it is depends on your application and your teams culture of writing tests. *There are no rules* when it comes to testing, only subjective ideas and thoughts. Some advocate for 100% code coverage and this can come from trying to force developers to write tests, or that someone is a neat freak, or that someone just finds it a nice and even number. I would say that 100% code coverage is rarely a good approach since it provides a lot of overhead both for your test suite and also in developer time. Finding the sweet-spot in your test suite is usually not a percentage number but instead of finding the right code to test and have good tests in place for it. Setters and getters are usually unnecessary pieces of code to test for example, but a class that deals with credit card payments should be thoroughly tested. 4 | 5 | This will not be a chapter on how, why and when to write tests, there are plenty of resources out there that are extensive and written by people with far better testing experience and knowledge. Instead this will be a chapter on the different types of tests and how they can or should fit into your deploy process. There are so many types of tests you can write that it would be impossible for me to cover all of them and instead I will cover the ones most commonly used in a PHP context. Testing is under constant debate without a clearly defined terminology with an array of terms being thrown around. I will not try to define types of tests but instead give a general term for them and explain the thought behind them since it's good to understand them, so you can leverage that instead of a label. Unfortunately though I must use a label when writing about them. 6 | 7 | What I do want to get across is that tests should always be part of your deploy process. Tests are perfect for automation and the full test suite should at some point run before code ends up in the production environment. 8 | 9 | ## 9.1 Unit testing 10 | 11 | The most basic of tests are unit tests and I do not believe there is another term used for these types of tests. A *unit* can be defined as a small, as small as possible in fact, piece of code that can be isolated and subjected to an automated test. They are fast, sharp and precise like a frickin' laser beam. Also no tests should depend on the state of another test but each one should run on a clean slate. The most common tool used for unit testing in the PHP world is PHPUnit and I doubt there will be a successor to it soon. 12 | 13 | Unit tests are all about finding bugs as soon as possible. It's a kind of first line defense against errors in your code and with a [TDD](https://en.wikipedia.org/wiki/Test-driven_development) workflow, where you write tests first, you can often reduce the amount of possible bugs down the road. Write a test, watch it fail, write code, check if code passes through tests, rinse and repeat. The TDD world is not without opponents of course and there was a lot of talk going on in the developer community when David Heinemeier Hansson release his blog post on ["TDD is dead. Long live testing."](http://david.heinemeierhansson.com/2014/tdd-is-dead-long-live-testing.html) back in 2014. Take a look at both camps and see what you find. 14 | 15 | ## 9.2 Acceptance testing 16 | 17 | In the agile software world we work with specifications and use cases. These will often be transformed into a specification of behavior, or business logic that a certain feature should comply with. Acceptance testing is used if you use behavior driven development (BDD) with tools such as [Behat](http://behat.org/), [PHPSpec](http://www.phpspec.net/), etc. In some cases these tests will use Selenium or Mink to run automated tests against a browser which makes it questionable if it's an acceptance test at that point, in other cases these tests will be quite fast to run. I argue that in cases of automated tests against a browser it's an end-to-end test. 18 | 19 | When performing an acceptance test you're trying to answer the question if the feature was built correctly according to a specification. A test could run against a user story that is converted to the Cucumber language, such as this user registration feature: 20 | 21 | ``` 22 | Feature: User registration 23 | 24 | Scenario: Registration form submission 25 | Given I am a guest user 26 | And I enter the email “foo@bar.com” 27 | And I enter the password “abc123” 28 | When I submit the registration form 29 | Then I should have a user registered with the email “foo@bar.com” 30 | ``` 31 | 32 | This type of testing is great since it’s a very readable format that can be shared between developers and non-developers so they can agree on the specification before the developer implements it. It removes language barriers and translation layers, and allows for better communication. If you’re more interested in BDD I suggest you take a look at the talk [Taking back BDD](https://skillsmatter.com/skillscasts/6240-taking-back-bdd) by Konstantin Kudryashov. 33 | 34 | ## 9.3 End-to-end testing 35 | 36 | These automated tests run against an emulated browser which often makes them slow to run, but they can ensure software quality against an entire system (such as a web page). They could perhaps make sure users are able to get through the entire process of registering, logging in or making a credit card payment for example. The test runner needs to start a browser and click through the process while waiting for the browser like a regular user which can be very time consuming. 37 | 38 | A common tool used for this in PHP is [Mink](http://mink.behat.org/) which has support for a various number of drivers that can emulate web browsers. The different drivers have different modes of operation and in that way have different speed as well. Some are just headless browsers that are quite fast but lack support for Javascript, would you want to test a registration form that sends an XHR-request for example you’re out of luck with them. But then there are some that can start full feature web browsers such as Chrome, Firefox or Internet Explorer, but these are a lot slower since a browser needs to be started and the tests will have to use it as a regular user would. 39 | 40 | ## 9.4 Manual testing 41 | 42 | Unless your doing continuous deployment you will do manual testing somewhere, whether it is only in your local environment by yourself or by a quality assurance (QA) team/person. You pretty much sit like a monkey and click on and type in various things to make sure it doesn’t break, you’re probably trying your best to make it break. That was maybe harsh on QA-people, it takes skill and experience to perform good manual tests and trying to find the edge cases where a feature can break. 43 | 44 | You should try to apply *hallway usability testing* whenever possible which is defined by Joel Spolsky in the classic [The Joel Test: 12 Steps to Better Code](http://www.joelonsoftware.com/articles/fog0000000043.html) as: 45 | 46 | > A hallway usability test is where you grab the next person that passes by in the hallway and force them to try to use the code you just wrote. If you do this to five people, you will learn 95% of what there is to learn about usability problems in your code. 47 | 48 | With this technique you will find a lot of usability issues and bugs you can fix before deploying. Do this as early as possible in your chain of environments to make sure you catch stuff you then have time to fix. Doing this on a complete release branch pushed to staging could end up delaying the deploy or lead to a *we’ll fix it later* issue. 49 | 50 | ## 9.5 What, where and when 51 | 52 | To fit the tests into your deploy process is determining what should run where and when. If you have a large application with lots of tests that takes a long time to run, running your test suite on each commit is probably going to annoy the hell out of every developer. You want your test suite to be as fast as possible of course, but some tests such as end-to-end tests will probably be slow. You need to find a good balance here, ultimately we want to find any errors before our code gets deployed in production. 53 | 54 | In a best case scenario you have a continuous integration environment, such as TravisCI, Jenkins or CodeShip to name a few. It can be responsible of picking up changes and running through your entire test suite once you push to a repository. If you do not have this luxury I would recommend running any time consuming tests as early as possible just after a developers local environment. Developers should be able to run unit and acceptance tests in their local environment without any issue since they should be fast. But other than that it’s good to run them in your development or staging environment once a developer pushes changes to the main repository. Having a set up where running the entire test suite in your development environment that pushes all the changes to your staging environment on a successful test run is to strive for. Most important is to **automate running tests**. 55 | 56 | ## 9.6 Failing tests 57 | 58 | So how would you deal with a failing test? Ignoring failing, incomplete or skipped tests will lead to code rot in your test suite and I advice you to never let this happen. In accordance to the broken window theory it will likely end up with no new tests being written, old tests not getting fixed properly and eventually you will have to scrap and rewrite your test suite. Going back to Joel Spolsky’s [The Joel Test: 12 Steps to Better Code](http://www.joelonsoftware.com/articles/fog0000000043.html), number five on the list is *Do you fix bugs before writing new code?*. This one can not really be applied here but I suggest a new bullet to the list: *Do you fix tests before making a deploy to production?*. 59 | 60 | It’s important to **stop** your deploy process once a test fails, make sure the right people gets **notified** and that it’s managed as a prioritized task to fix the code or the test to make it pass. A deploy with any kind of failing tests should never be allowed to complete in your production environment. This is true even if you know why a test is failing and know that nothing is broken. Fix it straight away and start the deploy process again. It will promote a good culture and a good test suite for your application that is not subject to rot. -------------------------------------------------------------------------------- /manuscript/10-logs-and-notifications.md: -------------------------------------------------------------------------------- 1 | # 10. Logs and notifications 2 | 3 | In the [list in the goals chapter](#goals-list) we discussed that we want our deploys to be *responsive*. We want it to be aware of what is happening and respond to that in certain ways, for example when there an error. 4 | 5 | Since we're always dealing with running certain commands in our deploy process, through tools or running arbitrary commands, we will have console output. Output can be verbose sometimes and that is more than often a good thing and something we can use to our advantage. Capturing output to log files or some other storage can be crucial in troubleshooting deploys that goes sideways. A silent failure in your deploy can take a lot of time and resources in debugging and fixing. If you capture output and store it somewhere where you or others can find it will make it easier. 6 | 7 | But we don't just want to just capture the output. We also want to know when something goes the way it should or when something goes wrong. We can notify people about this through various channels. Getting a notification when a deploy is successful or when an error occurs and it is aborted gives peace of mind. A good example can be that after a successful deploy you get a notification somehow saying that "Deploy successful: [11c287] Fixed bug #215", here you can easily see that the deploy was successful and that the latest commit deployed is *11c287*. The same applies (even more so) for "Deploy failed: could not migrate database". In that event you hopefully have a log somewhere where you can go and find out exactly what made the database migration fail, maybe it could not connect to the database or it tried to perform an operation that was not supported. 8 | 9 | ## 10.1 Saving logs 10 | 11 | There are two kinds of deploys and they need to be handled according to how they operate. The first kind is running a tool on your local computer that sends commands to your server(s) for deploy, this is true for tools such as Capistrano and Rocketeer. Then there is the other kind which runs on the server side under certain conditions, for example when using Git hooks or Phing. So a deploy could in a sense happen on the server side or through the client side. 12 | 13 | When performing a deploy through the client side you most likely won't have to save your logs, since you will get the full output in your terminal. Exporting this to some central log storage would probably be a complex and unnecessary procedure. You will most likely get a clear idea of what happened through the output of your tool. But when a deploy runs server side it becomes important to save the log(s). Where you save them is not really important as long as everybody can find them easily, a timestamped log file on each separate server is probably sufficient. 14 | 15 | When running a bash script, like demonstrated with Git hooks in the tools chapter, you can easily capture all output to a timestamped log file using this: 16 | 17 | {lang=bash} 18 | ~~~ 19 | #!/bin/bash 20 | 21 | NOW=$(date +"%Y%m%d-%H%M%S") 22 | deploy_log="/var/logs/deploy/deploy-$NOW.log" 23 | touch $deploy_log 24 | exec > >(tee $deploy_log) 25 | exec 2>&1 26 | 27 | # The rest of your deploy commands... 28 | ~~~ 29 | 30 | Any commands that is executed after that will be saved in that log file. 31 | 32 | If you don't have a bash script and you instead send a remote command to your server over SSH for example, there's a simple trick to achieve this also. Say you have a server running Jenkins that you deploy through, it runs all the tests and then send a remote command to your server(s) telling it to make a Phing build. You could then send the command: 33 | 34 | phing build > /var/logs/deploy/deploy-`date +"%Y%m%d-%H%M%S"`.log 2>&1 35 | 36 | This is possible with any kind of command you can run on your server to capture all output into a log file. 37 | 38 | ## 10.2 Types of notification 39 | 40 | There are a few ways of notifying people, whether that is when things run smoothly or when things go awry. Which type of notification you'll use will likely depend on the current technology your company or teams use for communication. Here are a few examples of channels you can use for notifications. 41 | 42 | {pagebreak} 43 | 44 | ### E-mail 45 | 46 | The most conventional way of sending out notifications is with a good old fashioned e-mail. There are a few upsides to sending notifications through e-mails, one being that you aren't limited to a certain number of characters or lines. You can append full log output in your e-mails for example. One other upside is that everyone has an e-mail address, creating lists and sending notifications to those is a great way of ensuring that people receive and read them. Just make sure that you keep your lists up to date, adding new and removing old people as they come and go. 47 | 48 | | Tool | Plugin | 49 | | ---------- | -------------------------------------------------------------------------------- | 50 | | Git hooks | [mail][email-1], [sendmail][email-2] | 51 | | Phing | [MailTask][email-3] | 52 | | Capistrano | [gofullstack/capistrano-notifier][email-4], [pboling/capistrano_mailer][email-5] | 53 | | Rocketeer | [Write custom task][email-6] | 54 | 55 | [email-1]: http://linux.die.net/man/1/mail 56 | [email-2]: http://www.sendmail.org/~ca/email/man/sendmail.html 57 | [email-3]: https://www.phing.info/docs/guide/trunk/MailTask.html 58 | [email-4]: https://github.com/gofullstack/capistrano-notifier 59 | [email-5]: https://github.com/pboling/capistrano_mailer 60 | [email-6]: http://rocketeer.autopergamene.eu/#/docs/docs/II-Concepts/Tasks 61 | 62 | {pagebreak} 63 | 64 | ### Slack 65 | 66 | Slack is the rising star of group communication for companies and their teams, for good reasons. Having a channel for your deploys can be a good way of the right people getting notified of relevant information. They have an API which makes it really easy to send notifications, and there are many plugins that has been built to use it. If a plugin does not exists for your deploy tool you can always use cURL or something similar for sending requests to it. 67 | 68 | | Tool | Plugin | 69 | | ---------- | ------------------------------------------------------------------------- | 70 | | Git hooks | [API][slack-1] | 71 | | Phing | [API][slack-1] | 72 | | Capistrano | [j-mcnally/capistrano-slack][slack-2], [phallstrom/slackistrano][slack-3] | 73 | | Rocketeer | [rocketeers/rocketeer-slack][slack-4] | 74 | 75 | [slack-1]: https://api.slack.com/ 76 | [slack-2]: https://github.com/j-mcnally/capistrano-slack 77 | [slack-3]: https://github.com/phallstrom/slackistrano 78 | [slack-4]: https://github.com/rocketeers/rocketeer-slack 79 | 80 | {pagebreak} 81 | 82 | ### HipChat 83 | 84 | Still a popular alternative even though Slack seems to be taking more and more of the market. It is also a great tool for communication for companies and teams. Many plugins exists here as well since they have an API for sending notifications, which also always leave you the option of sending a cURL request or similar for interacting with it. 85 | 86 | | Tool | Plugin | 87 | | ---------- | ------------------------------------ | 88 | | Git hooks | [API][hipchat-1] 89 | | Phing | [rcrowe/phing-hipchat][hipchat-2] | 90 | | Capistrano | [hipchat/hipchat-rb][hipchat-3], [restorando/capistrano-hipchat][hipchat-4] | 91 | | Rocketeer | [rocketeers/rocketeer-hipchat][hipchat-5] | 92 | 93 | [hipchat-1]: https://www.hipchat.com/docs/apiv2 94 | [hipchat-2]: https://github.com/rcrowe/phing-hipchat 95 | [hipchat-3]: https://github.com/hipchat/hipchat-rb 96 | [hipchat-4]: https://github.com/restorando/capistrano-hipchat 97 | [hipchat-5]: https://github.com/rocketeers/rocketeer-hipchat 98 | 99 | {pagebreak} 100 | 101 | ### IRC 102 | 103 | The technology that never seems to go out of fashion. It's commonly used and have an impressive track record, it has been around since 1988! The amount of characters you can include in your messages might be a bit limiting though, full log outputs is not to consider here. But a "Deploy successful: [11c287] Fixed bug #215" is often enough as a message. 104 | 105 | | Tool | Plugin | 106 | | ---------- | -------------------------------------------------------------------------- | 107 | | Git hooks | [Send message to IRC channel from bash][irc-1] | 108 | | Phing | [Send message to IRC channel from bash][irc-1] | 109 | | Capistrano | [ursm/capistrano-notification][irc-2], [linyows/capistrano-ikachan][irc-3] | 110 | | Rocketeer | [Mochaka/rocketeer-irc][irc-4] | 111 | 112 | [irc-1]: http://serverfault.com/questions/183157/send-message-to-irc-channel-from-bash 113 | [irc-2]: https://github.com/ursm/capistrano-notification 114 | [irc-3]: https://github.com/linyows/capistrano-ikachan 115 | [irc-4]: https://github.com/Mochaka/rocketeer-irc 116 | 117 | {pagebreak} 118 | 119 | ## 10.3 Useful git commands 120 | 121 | If you're following the git-flow branching model in the [version control chapter](#chapter-version-control), there are some useful git commands you can use. You can leverage the git binary and capture output which you then can include in your notifications. Of course you can capture output for any CLI command that you use to include in your notifications, git is just one example and these are a handful of useful ones. 122 | 123 | **Current branch** 124 | 125 | Displaying the current branch can be good to make sure that the right branch is deployed. 126 | 127 | git rev-parse --abbrev-ref HEAD 128 | 129 | **Commit at HEAD** 130 | 131 | It's never a bad idea to show where your HEAD pointer is. This tells you exactly where your deploy is. 132 | 133 | git --no-pager log --abbrev-commit --pretty=oneline -1 134 | 135 | **Latest tag** 136 | 137 | Including your latest tag in a notification is great since it will tell which version that was just deployed. 138 | 139 | # For current branch 140 | git describe --abbrev=0 --tags 141 | 142 | # Across all branches 143 | git describe --tags $(git rev-list --tags --max-count=1) 144 | 145 | **Commits between latest tag and previous tag** 146 | 147 | This command allows you to generate a simple changelog with all commits that was deployed. Excellent for appending as complementary information to your notification. 148 | 149 | git --no-pager log --abbrev-commit --pretty=oneline $(git rev-list --tags --max-count=1)...$(git describe --abbrev=0 $(git describe --tags $(git rev-list --tags --max-count=1)^)) 150 | -------------------------------------------------------------------------------- /manuscript/11-conclusion.md: -------------------------------------------------------------------------------- 1 | # Conclusion 2 | 3 | Thank you so much for purchasing this book and reading it through to the end. This has been a very inspiring and rewarding experience for me, and I hope that you have enjoyed the book and learned things. If you did enjoy it I would appreciate if you spread the word about it. Tweet it, recommend it to friends, share the link, or perhaps talk about it at a meetup you are organizing, speaking at or just attending. 4 | 5 | To my best ability I've tried to mix theory with practice. This kind of book would get long and boring if no applicable technical aspect were covered. That is why I covered deploy tools and tried to include code in different chapters, to balance it all out. 6 | 7 | I do not know when I will consider my work done on this book. I will continue to revise and add to it from time to time. Deploy tools is something I will continue adding for example. We all know how fast technology moves and I think it is important to try and keep up with it in this book. If you find anything that is outdated or something you find should be added to this book, don't hesitate to contact me. 8 | 9 | Thanks again! -------------------------------------------------------------------------------- /manuscript/Book.txt: -------------------------------------------------------------------------------- 1 | {frontmatter} 2 | 00-introduction.md 3 | 4 | {mainmatter} 5 | 01-automate.md 6 | 02-goals.md 7 | 03-environments.md 8 | 04-atomic-deploys.md 9 | 05-tools.md 10 | 06-version-control.md 11 | 07-dependencies.md 12 | 08-database-migrations.md 13 | 09-running-tests.md 14 | 10-logs-and-notifications.md 15 | 11-conclusion.md 16 | -------------------------------------------------------------------------------- /manuscript/Sample.txt: -------------------------------------------------------------------------------- 1 | {frontmatter} 2 | 00-introduction.md 3 | 4 | {mainmatter} 5 | 01-automate.md 6 | -------------------------------------------------------------------------------- /manuscript/code/chapter-3/env-function.php: -------------------------------------------------------------------------------- 1 | function env($key, $default = null) 2 | { 3 | $value = getenv($key); 4 | 5 | if ($value === false) { 6 | return $default instanceof Closure ? $default() : $default; 7 | } 8 | 9 | switch (strtolower($value)) { 10 | case 'true': 11 | case '(true)': 12 | return true; 13 | case 'false': 14 | case '(false)': 15 | return false; 16 | case 'empty': 17 | case '(empty)': 18 | return ''; 19 | case 'null': 20 | case '(null)': 21 | return; 22 | } 23 | 24 | return $value; 25 | } -------------------------------------------------------------------------------- /manuscript/code/chapter-5/buildfile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/capistrano-app/Capfile: -------------------------------------------------------------------------------- 1 | # Load DSL and set up stages 2 | require 'capistrano/setup' 3 | 4 | # Include default deployment tasks 5 | require 'capistrano/deploy' 6 | 7 | # Include tasks from other gems included in your Gemfile 8 | # 9 | # For documentation on these, see for example: 10 | # 11 | # https://github.com/capistrano/rvm 12 | # https://github.com/capistrano/rbenv 13 | # https://github.com/capistrano/chruby 14 | # https://github.com/capistrano/bundler 15 | # https://github.com/capistrano/rails 16 | # https://github.com/capistrano/passenger 17 | # 18 | # require 'capistrano/rvm' 19 | # require 'capistrano/rbenv' 20 | # require 'capistrano/chruby' 21 | # require 'capistrano/bundler' 22 | # require 'capistrano/rails/assets' 23 | # require 'capistrano/rails/migrations' 24 | # require 'capistrano/passenger' 25 | 26 | require 'capistrano/composer' 27 | 28 | require 'capistrano/file-permissions' 29 | 30 | # Load custom tasks from `lib/capistrano/tasks` if you have any defined 31 | Dir.glob('lib/capistrano/tasks/*.rake').each { |r| import r } 32 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/capistrano-app/config/deploy.rb: -------------------------------------------------------------------------------- 1 | # config valid only for current version of Capistrano 2 | lock '3.4.0' 3 | 4 | set :application, 'my-app' 5 | set :repo_url, 'https://github.com/modess/deploy-test-application.git' 6 | 7 | # Default branch is :master 8 | # ask :branch, `git rev-parse --abbrev-ref HEAD`.chomp 9 | 10 | # Default deploy_to directory is /var/www/my_app_name 11 | set :deploy_to, '/var/www/app' 12 | 13 | # Default value for :scm is :git 14 | # set :scm, :git 15 | 16 | # Default value for :format is :pretty 17 | # set :format, :pretty 18 | 19 | # Default value for :log_level is :debug 20 | # set :log_level, :debug 21 | 22 | # Default value for :pty is false 23 | # set :pty, true 24 | 25 | # Default value for :linked_files is [] 26 | # set :linked_files, fetch(:linked_files, []).push('config/database.yml', 'config/secrets.yml') 27 | 28 | # Default value for linked_dirs is [] 29 | set :linked_dirs, fetch(:linked_dirs, []).push('storage') 30 | 31 | # Default value for default_env is {} 32 | # set :default_env, { path: "/opt/ruby/bin:$PATH" } 33 | 34 | # Default value for keep_releases is 5 35 | # set :keep_releases, 5 36 | 37 | set :file_permissions_paths, ["storage"] 38 | before "deploy:updated", "deploy:set_permissions:acl" 39 | 40 | namespace :deploy do 41 | 42 | after :restart, :clear_cache do 43 | on roles(:web), in: :groups, limit: 3, wait: 10 do 44 | # Here we can do anything such as: 45 | # within release_path do 46 | # execute :rake, 'cache:clear' 47 | # end 48 | end 49 | end 50 | 51 | end 52 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/capistrano-app/config/deploy/production.rb: -------------------------------------------------------------------------------- 1 | # server-based syntax 2 | # ====================== 3 | # Defines a single server with a list of roles and multiple properties. 4 | # You can define all roles on a single server, or split them: 5 | 6 | server 'my-app.com', user: 'niklas', roles: %w{app} 7 | # server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value 8 | # server 'db.example.com', user: 'deploy', roles: %w{db} 9 | 10 | 11 | 12 | # role-based syntax 13 | # ================== 14 | 15 | # Defines a role with one or multiple servers. The primary server in each 16 | # group is considered to be the first unless any hosts have the primary 17 | # property set. Specify the username and a domain or IP for the server. 18 | # Don't use `:all`, it's a meta role. 19 | 20 | # role :app, %w{deploy@example.com}, my_property: :my_value 21 | # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value 22 | # role :db, %w{deploy@example.com} 23 | 24 | 25 | 26 | # Configuration 27 | # ============= 28 | # You can set any configuration variable like in config/deploy.rb 29 | # These variables are then only loaded and set in this stage. 30 | # For available Capistrano configuration variables see the documentation page. 31 | # http://capistranorb.com/documentation/getting-started/configuration/ 32 | # Feel free to add new variables to customise your setup. 33 | 34 | 35 | 36 | # Custom SSH Options 37 | # ================== 38 | # You may pass any option but keep in mind that net/ssh understands a 39 | # limited set of options, consult the Net::SSH documentation. 40 | # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start 41 | # 42 | # Global options 43 | # -------------- 44 | # set :ssh_options, { 45 | # keys: %w(/home/rlisowski/.ssh/id_rsa), 46 | # forward_agent: false, 47 | # auth_methods: %w(password) 48 | # } 49 | # 50 | # The server-based syntax can be used to override options: 51 | # ------------------------------------ 52 | # server 'example.com', 53 | # user: 'user_name', 54 | # roles: %w{web app}, 55 | # ssh_options: { 56 | # user: 'user_name', # overrides user setting above 57 | # keys: %w(/home/user_name/.ssh/id_rsa), 58 | # forward_agent: false, 59 | # auth_methods: %w(publickey password) 60 | # # password: 'please use keys' 61 | # } 62 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/capistrano-app/config/deploy/staging.rb: -------------------------------------------------------------------------------- 1 | # server-based syntax 2 | # ====================== 3 | # Defines a single server with a list of roles and multiple properties. 4 | # You can define all roles on a single server, or split them: 5 | 6 | # server 'example.com', user: 'deploy', roles: %w{app db web}, my_property: :my_value 7 | # server 'example.com', user: 'deploy', roles: %w{app web}, other_property: :other_value 8 | # server 'db.example.com', user: 'deploy', roles: %w{db} 9 | 10 | 11 | 12 | # role-based syntax 13 | # ================== 14 | 15 | # Defines a role with one or multiple servers. The primary server in each 16 | # group is considered to be the first unless any hosts have the primary 17 | # property set. Specify the username and a domain or IP for the server. 18 | # Don't use `:all`, it's a meta role. 19 | 20 | # role :app, %w{deploy@example.com}, my_property: :my_value 21 | # role :web, %w{user1@primary.com user2@additional.com}, other_property: :other_value 22 | # role :db, %w{deploy@example.com} 23 | 24 | 25 | 26 | # Configuration 27 | # ============= 28 | # You can set any configuration variable like in config/deploy.rb 29 | # These variables are then only loaded and set in this stage. 30 | # For available Capistrano configuration variables see the documentation page. 31 | # http://capistranorb.com/documentation/getting-started/configuration/ 32 | # Feel free to add new variables to customise your setup. 33 | 34 | 35 | 36 | # Custom SSH Options 37 | # ================== 38 | # You may pass any option but keep in mind that net/ssh understands a 39 | # limited set of options, consult the Net::SSH documentation. 40 | # http://net-ssh.github.io/net-ssh/classes/Net/SSH.html#method-c-start 41 | # 42 | # Global options 43 | # -------------- 44 | # set :ssh_options, { 45 | # keys: %w(/home/rlisowski/.ssh/id_rsa), 46 | # forward_agent: false, 47 | # auth_methods: %w(password) 48 | # } 49 | # 50 | # The server-based syntax can be used to override options: 51 | # ------------------------------------ 52 | # server 'example.com', 53 | # user: 'user_name', 54 | # roles: %w{web app}, 55 | # ssh_options: { 56 | # user: 'user_name', # overrides user setting above 57 | # keys: %w(/home/user_name/.ssh/id_rsa), 58 | # forward_agent: false, 59 | # auth_methods: %w(publickey password) 60 | # # password: 'please use keys' 61 | # } 62 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/git-hooks-atomic-deploy/post-receive: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | unset GIT_DIR 4 | 5 | env -i git reset --hard 6 | git checkout -f 7 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/git-hooks-atomic-deploy/post-update: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | unset GIT_DIR 4 | 5 | NOW=$(date +"%Y%m%d-%H%M%S") 6 | deploy_log="/var/logs/deploy/deploy-$NOW.log" 7 | touch $deploy_log 8 | exec > >(tee $deploy_log) 9 | exec 2>&1 10 | 11 | # Variables 12 | NOW=$(date +"%Y%m%d-%H%M%S") 13 | APPLICATION_DIR="`pwd`" 14 | ROOT_DIR="`cd .. && pwd`" 15 | BUILDS_DIR="$ROOT_DIR/builds" 16 | BUILD_LOCATION="$BUILDS_DIR/$NOW" 17 | SHARED_DIR="$ROOT_DIR/shared" 18 | LATEST_SYMLINK="$ROOT_DIR/latest" 19 | 20 | # Update dependencies 21 | composer update --no-dev 22 | 23 | # Add symbolic links for shared data 24 | rm -rf app/storage 25 | ln -s $SHARED_DIR/storage app/storage 26 | 27 | # Copy build 28 | cp -rp $APPLICATION_DIR $BUILD_LOCATION 29 | 30 | # Symbolic link from build to latest 31 | ln -nsf $BUILD_LOCATION $LATEST_SYMLINK 32 | 33 | # Remove older builds, keep 5 34 | cd $BUILDS_DIR 35 | rm -rf $(ls -1t | tail -n +6) 36 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/phing-atomic-deploy/buildfile.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/post-receive: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | unset GIT_DIR 4 | 5 | env -i git reset --hard 6 | git checkout -f -------------------------------------------------------------------------------- /manuscript/code/chapter-5/post-update: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | cd .. 3 | unset GIT_DIR 4 | 5 | # Variables 6 | NOW=$(date +"%Y%m%d-%H%M%S") 7 | APPLICATION_DIR="`pwd`" 8 | ROOT_DIR="`cd .. && pwd`" 9 | BUILDS_DIR="$ROOT_DIR/builds" 10 | BUILD_LOCATION="$BUILDS_DIR/$NOW" 11 | SHARED_DIR="$ROOT_DIR/shared" 12 | LATEST_SYMLINK="$ROOT_DIR/latest" 13 | 14 | # Update dependencies 15 | composer update --no-dev 16 | 17 | # Add symbolic links for shared data 18 | rm -rf app/storage 19 | ln -s $SHARED_DIR/storage app/storage 20 | 21 | # Copy build 22 | cp -rp $APPLICATION_DIR $BUILD_LOCATION 23 | 24 | # Symbolic link from build to latest 25 | ln -nsf $BUILD_LOCATION $LATEST_SYMLINK 26 | 27 | # Remove older builds, keep 5 28 | cd $BUILDS_DIR 29 | rm -rf $(ls -1t | tail -n +6) -------------------------------------------------------------------------------- /manuscript/code/chapter-5/rocketeer-app/.rocketeer/config.php: -------------------------------------------------------------------------------- 1 | 'my-app', 11 | 12 | // Plugins 13 | //////////////////////////////////////////////////////////////////// 14 | 15 | // The plugins to load 16 | 'plugins' => [// 'Rocketeer\Plugins\Slack\RocketeerSlack', 17 | ], 18 | 19 | // Logging 20 | //////////////////////////////////////////////////////////////////// 21 | 22 | // The schema to use to name log files 23 | 'logs' => function (ConnectionsHandler $connections) { 24 | return sprintf('%s-%s-%s.log', $connections->getConnection(), $connections->getStage(), date('Ymd')); 25 | }, 26 | 27 | // Remote access 28 | // 29 | // You can either use a single connection or an array of connections 30 | //////////////////////////////////////////////////////////////////// 31 | 32 | // The default remote connection(s) to execute tasks on 33 | 'default' => ['production'], 34 | 35 | // The various connections you defined 36 | // You can leave all of this empty or remove it entirely if you don't want 37 | // to track files with credentials : Rocketeer will prompt you for your credentials 38 | // and store them locally 39 | 'connections' => [ 40 | 'production' => [ 41 | 'host' => 'my-app.com', 42 | 'username' => 'niklas', 43 | 'password' => '', 44 | 'key' => '/Users/niklas/.ssh/id_rsa', 45 | 'keyphrase' => '', 46 | 'agent' => '', 47 | 'db_role' => true, 48 | ], 49 | ], 50 | 51 | /* 52 | * In most multiserver scenarios, migrations must be run in an exclusive server. 53 | * In the event of not having a separate database server (in which case it can 54 | * be handled through connections), you can assign a 'db_role' => true to the 55 | * server's configuration and it will only run the migrations in that specific 56 | * server at the time of deployment. 57 | */ 58 | 'use_roles' => false, 59 | 60 | // Contextual options 61 | // 62 | // In this section you can fine-tune the above configuration according 63 | // to the stage or connection currently in use. 64 | // Per example : 65 | // 'stages' => array( 66 | // 'staging' => array( 67 | // 'scm' => array('branch' => 'staging'), 68 | // ), 69 | // 'production' => array( 70 | // 'scm' => array('branch' => 'master'), 71 | // ), 72 | // ), 73 | //////////////////////////////////////////////////////////////////// 74 | 75 | 'on' => [ 76 | 77 | // Stages configurations 78 | 'stages' => [], 79 | // Connections configuration 80 | 'connections' => [], 81 | 82 | ], 83 | 84 | ]; 85 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/rocketeer-app/.rocketeer/hooks.php: -------------------------------------------------------------------------------- 1 | [ 19 | 'setup' => [], 20 | 'deploy' => [], 21 | 'cleanup' => [], 22 | ], 23 | 24 | // Tasks to execute after the core Rocketeer Tasks 25 | 'after' => [ 26 | 'setup' => [], 27 | 'deploy' => [ 28 | function($task) { 29 | $task->command->info('Latest commit for the repository:'); 30 | $task->runForCurrentRelease('git --no-pager log --decorate=short --pretty=oneline --abbrev-commit -n1'); 31 | } 32 | ], 33 | 'cleanup' => [], 34 | ], 35 | 36 | // Custom Tasks to register with Rocketeer 37 | 'custom' => [], 38 | 39 | ]; 40 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/rocketeer-app/.rocketeer/logs/.gitignore: -------------------------------------------------------------------------------- 1 | *.* 2 | !.gitignore 3 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/rocketeer-app/.rocketeer/paths.php: -------------------------------------------------------------------------------- 1 | '', 17 | 18 | // Path to Composer 19 | 'composer' => '', 20 | 21 | // Path to the Artisan CLI 22 | 'artisan' => 'artisan', 23 | 24 | ]; 25 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/rocketeer-app/.rocketeer/remote.php: -------------------------------------------------------------------------------- 1 | [ 11 | 'directory_separator' => '/', 12 | 'line_endings' => "\n", 13 | ], 14 | 15 | // The number of releases to keep at all times 16 | 'keep_releases' => 4, 17 | 18 | // Folders 19 | //////////////////////////////////////////////////////////////////// 20 | 21 | // The root directory where your applications will be deployed 22 | // This path *needs* to start at the root, ie. start with a / 23 | 'root_directory' => '/var/www/', 24 | 25 | // The folder the application will be cloned in 26 | // Leave empty to use `application_name` as your folder name 27 | 'app_directory' => 'myapp', 28 | 29 | // A list of folders/file to be shared between releases 30 | // Use this to list folders that need to keep their state, like 31 | // user uploaded data, file-based databases, etc. 32 | 'shared' => [ 33 | 'storage', 34 | ], 35 | 36 | // Execution 37 | ////////////////////////////////////////////////////////////////////// 38 | 39 | // If enabled will force a shell to be created 40 | // which is required for some tools like RVM or NVM 41 | 'shell' => false, 42 | 43 | // An array of commands to run under shell 44 | 'shelled' => ['which', 'ruby', 'npm', 'bower', 'bundle', 'grunt'], 45 | 46 | // Enable use of sudo for some commands 47 | // You can specify a sudo user by doing 48 | // 'sudo' => 'the_user' 49 | 'sudo' => false, 50 | 51 | // An array of commands to run under sudo 52 | 'sudoed' => [], 53 | 54 | // Permissions$ 55 | //////////////////////////////////////////////////////////////////// 56 | 57 | 'permissions' => [ 58 | 59 | // The folders and files to set as web writable 60 | 'files' => [ 61 | 'storage', 62 | ], 63 | 64 | // Here you can configure what actions will be executed to set 65 | // permissions on the folder above. The Closure can return 66 | // a single command as a string or an array of commands 67 | 'callback' => function ($task, $file) { 68 | return [ 69 | sprintf('chmod -R 755 %s', $file), 70 | sprintf('chmod -R g+s %s', $file), 71 | sprintf('chown -R www-data:www-data %s', $file), 72 | ]; 73 | }, 74 | 75 | ], 76 | 77 | ]; 78 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/rocketeer-app/.rocketeer/scm.php: -------------------------------------------------------------------------------- 1 | 'git', 10 | 11 | // The SSH/HTTPS address to your repository 12 | // Example: https://github.com/vendor/website.git 13 | 'repository' => 'https://github.com/modess/deploy-test-application.git', 14 | 15 | // The repository credentials : you can leave those empty 16 | // if you're using SSH or if your repository is public 17 | // In other cases you can leave this empty too, and you will 18 | // be prompted for the credentials on deploy. If you don't want 19 | // to be prompted (public repo, etc) set the values to null 20 | 'username' => '', 21 | 'password' => '', 22 | 23 | // The branch to deploy 24 | 'branch' => 'master', 25 | 26 | // Whether your SCM should do a "shallow" clone of the repository 27 | // or not – this means a clone with just the latest state of your 28 | // application (no history) 29 | // If you're having problems cloning, try setting this to false 30 | 'shallow' => true, 31 | 32 | // Recursively pull in submodules. Works only with GIT. 33 | 'submodules' => true, 34 | 35 | ]; 36 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/rocketeer-app/.rocketeer/stages.php: -------------------------------------------------------------------------------- 1 | [], 14 | 15 | // The default stage to execute tasks on when --stage is not provided 16 | // Falsey means all of them 17 | 'default' => '', 18 | 19 | ]; 20 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/rocketeer-app/.rocketeer/strategies.php: -------------------------------------------------------------------------------- 1 | 7 | * 8 | * For the full copyright and license information, please view the LICENSE 9 | * file that was distributed with this source code. 10 | */ 11 | 12 | use Rocketeer\Binaries\PackageManagers\Composer; 13 | use Rocketeer\Tasks\Subtasks\Primer; 14 | 15 | return [ 16 | 17 | // Task strategies 18 | // 19 | // Here you can configure in a modular way which tasks to use to 20 | // execute various core parts of your deployment's flow 21 | ////////////////////////////////////////////////////////////////////// 22 | 23 | // Which strategy to use to check the server 24 | 'check' => 'Php', 25 | 26 | // Which strategy to use to create a new release 27 | 'deploy' => 'Clone', 28 | 29 | // Which strategy to use to test your application 30 | 'test' => 'Phpunit', 31 | 32 | // Which strategy to use to migrate your database 33 | 'migrate' => 'Artisan', 34 | 35 | // Which strategy to use to install your application's dependencies 36 | 'dependencies' => 'Polyglot', 37 | 38 | // Execution hooks 39 | ////////////////////////////////////////////////////////////////////// 40 | 41 | 'composer' => [ 42 | 'install' => function (Composer $composer, $task) { 43 | return $composer->install([], ['--no-interaction' => null, '--no-dev' => null, '--prefer-dist' => null]); 44 | }, 45 | 'update' => function (Composer $composer) { 46 | return $composer->update(); 47 | }, 48 | ], 49 | 50 | // Here you can configure the Primer tasks 51 | // which will run a set of commands on the local 52 | // machine, determining whether the deploy can proceed 53 | // or not 54 | 'primer' => function (Primer $task) { 55 | return [ 56 | // $task->executeTask('Test'), 57 | // $task->binary('grunt')->execute('lint'), 58 | ]; 59 | }, 60 | 61 | ]; 62 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/targets.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /manuscript/code/chapter-5/tasks.xml: -------------------------------------------------------------------------------- 1 | 2 | recipient = $recipient; 8 | } 9 | 10 | function main() { 11 | $this->log("Hello " . $this->recipient . "!"); 12 | } 13 | } 14 | ]]> 15 | 16 | 17 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/.gitignore: -------------------------------------------------------------------------------- 1 | vendor/ 2 | scripts/batches 3 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "my-app", 3 | "require": { 4 | "robmorgan/phinx": "^0.4.4", 5 | "mustangostang/spyc": "^0.5.1" 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/composer.lock: -------------------------------------------------------------------------------- 1 | { 2 | "_readme": [ 3 | "This file locks the dependencies of your project to a known state", 4 | "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#composer-lock-the-lock-file", 5 | "This file is @generated automatically" 6 | ], 7 | "hash": "1b4174b40eec502581d63bb6aa77bb1f", 8 | "packages": [ 9 | { 10 | "name": "mustangostang/spyc", 11 | "version": "0.5.1", 12 | "source": { 13 | "type": "git", 14 | "url": "https://github.com/mustangostang/spyc.git", 15 | "reference": "dc4785b4d7227fd9905e086d499fb8abfadf9977" 16 | }, 17 | "dist": { 18 | "type": "zip", 19 | "url": "https://api.github.com/repos/mustangostang/spyc/zipball/dc4785b4d7227fd9905e086d499fb8abfadf9977", 20 | "reference": "dc4785b4d7227fd9905e086d499fb8abfadf9977", 21 | "shasum": "" 22 | }, 23 | "require": { 24 | "php": ">=5.3.1" 25 | }, 26 | "type": "library", 27 | "extra": { 28 | "branch-alias": { 29 | "dev-master": "0.5.x-dev" 30 | } 31 | }, 32 | "autoload": { 33 | "files": [ 34 | "Spyc.php" 35 | ] 36 | }, 37 | "notification-url": "https://packagist.org/downloads/", 38 | "license": [ 39 | "MIT License" 40 | ], 41 | "authors": [ 42 | { 43 | "name": "mustangostang", 44 | "email": "vlad.andersen@gmail.com" 45 | } 46 | ], 47 | "description": "A simple YAML loader/dumper class for PHP", 48 | "homepage": "https://github.com/mustangostang/spyc/", 49 | "keywords": [ 50 | "spyc", 51 | "yaml", 52 | "yml" 53 | ], 54 | "time": "2013-02-21 10:52:01" 55 | }, 56 | { 57 | "name": "robmorgan/phinx", 58 | "version": "v0.4.4", 59 | "source": { 60 | "type": "git", 61 | "url": "https://github.com/robmorgan/phinx.git", 62 | "reference": "b4b7d1e045d679278d4cb9ab18fd513f15f4797e" 63 | }, 64 | "dist": { 65 | "type": "zip", 66 | "url": "https://api.github.com/repos/robmorgan/phinx/zipball/b4b7d1e045d679278d4cb9ab18fd513f15f4797e", 67 | "reference": "b4b7d1e045d679278d4cb9ab18fd513f15f4797e", 68 | "shasum": "" 69 | }, 70 | "require": { 71 | "php": ">=5.3.2", 72 | "symfony/config": "~2.7", 73 | "symfony/console": "~2.7", 74 | "symfony/yaml": "~2.7" 75 | }, 76 | "require-dev": { 77 | "phpunit/phpunit": "3.7.*", 78 | "squizlabs/php_codesniffer": "dev-phpcs-fixer" 79 | }, 80 | "bin": [ 81 | "bin/phinx" 82 | ], 83 | "type": "library", 84 | "autoload": { 85 | "psr-4": { 86 | "Phinx\\": "src/Phinx" 87 | } 88 | }, 89 | "notification-url": "https://packagist.org/downloads/", 90 | "license": [ 91 | "MIT" 92 | ], 93 | "authors": [ 94 | { 95 | "name": "Rob Morgan", 96 | "email": "robbym@gmail.com", 97 | "homepage": "http://robmorgan.id.au", 98 | "role": "Lead Developer" 99 | }, 100 | { 101 | "name": "Woody Gilk", 102 | "email": "woody.gilk@gmail.com", 103 | "homepage": "http://shadowhand.me", 104 | "role": "Developer" 105 | } 106 | ], 107 | "description": "Phinx makes it ridiculously easy to manage the database migrations for your PHP app.", 108 | "homepage": "https://phinx.org", 109 | "keywords": [ 110 | "database", 111 | "database migrations", 112 | "db", 113 | "migrations", 114 | "phinx" 115 | ], 116 | "time": "2015-06-14 13:02:38" 117 | }, 118 | { 119 | "name": "symfony/config", 120 | "version": "v2.7.3", 121 | "source": { 122 | "type": "git", 123 | "url": "https://github.com/symfony/Config.git", 124 | "reference": "6c905bbed1e728226de656e4c07d620dfe9e80d9" 125 | }, 126 | "dist": { 127 | "type": "zip", 128 | "url": "https://api.github.com/repos/symfony/Config/zipball/6c905bbed1e728226de656e4c07d620dfe9e80d9", 129 | "reference": "6c905bbed1e728226de656e4c07d620dfe9e80d9", 130 | "shasum": "" 131 | }, 132 | "require": { 133 | "php": ">=5.3.9", 134 | "symfony/filesystem": "~2.3" 135 | }, 136 | "require-dev": { 137 | "symfony/phpunit-bridge": "~2.7" 138 | }, 139 | "type": "library", 140 | "extra": { 141 | "branch-alias": { 142 | "dev-master": "2.7-dev" 143 | } 144 | }, 145 | "autoload": { 146 | "psr-4": { 147 | "Symfony\\Component\\Config\\": "" 148 | } 149 | }, 150 | "notification-url": "https://packagist.org/downloads/", 151 | "license": [ 152 | "MIT" 153 | ], 154 | "authors": [ 155 | { 156 | "name": "Fabien Potencier", 157 | "email": "fabien@symfony.com" 158 | }, 159 | { 160 | "name": "Symfony Community", 161 | "homepage": "https://symfony.com/contributors" 162 | } 163 | ], 164 | "description": "Symfony Config Component", 165 | "homepage": "https://symfony.com", 166 | "time": "2015-07-09 16:07:40" 167 | }, 168 | { 169 | "name": "symfony/console", 170 | "version": "v2.7.3", 171 | "source": { 172 | "type": "git", 173 | "url": "https://github.com/symfony/Console.git", 174 | "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e" 175 | }, 176 | "dist": { 177 | "type": "zip", 178 | "url": "https://api.github.com/repos/symfony/Console/zipball/d6cf02fe73634c96677e428f840704bfbcaec29e", 179 | "reference": "d6cf02fe73634c96677e428f840704bfbcaec29e", 180 | "shasum": "" 181 | }, 182 | "require": { 183 | "php": ">=5.3.9" 184 | }, 185 | "require-dev": { 186 | "psr/log": "~1.0", 187 | "symfony/event-dispatcher": "~2.1", 188 | "symfony/phpunit-bridge": "~2.7", 189 | "symfony/process": "~2.1" 190 | }, 191 | "suggest": { 192 | "psr/log": "For using the console logger", 193 | "symfony/event-dispatcher": "", 194 | "symfony/process": "" 195 | }, 196 | "type": "library", 197 | "extra": { 198 | "branch-alias": { 199 | "dev-master": "2.7-dev" 200 | } 201 | }, 202 | "autoload": { 203 | "psr-4": { 204 | "Symfony\\Component\\Console\\": "" 205 | } 206 | }, 207 | "notification-url": "https://packagist.org/downloads/", 208 | "license": [ 209 | "MIT" 210 | ], 211 | "authors": [ 212 | { 213 | "name": "Fabien Potencier", 214 | "email": "fabien@symfony.com" 215 | }, 216 | { 217 | "name": "Symfony Community", 218 | "homepage": "https://symfony.com/contributors" 219 | } 220 | ], 221 | "description": "Symfony Console Component", 222 | "homepage": "https://symfony.com", 223 | "time": "2015-07-28 15:18:12" 224 | }, 225 | { 226 | "name": "symfony/filesystem", 227 | "version": "v2.7.3", 228 | "source": { 229 | "type": "git", 230 | "url": "https://github.com/symfony/Filesystem.git", 231 | "reference": "2d7b2ddaf3f548f4292df49a99d19c853d43f0b8" 232 | }, 233 | "dist": { 234 | "type": "zip", 235 | "url": "https://api.github.com/repos/symfony/Filesystem/zipball/2d7b2ddaf3f548f4292df49a99d19c853d43f0b8", 236 | "reference": "2d7b2ddaf3f548f4292df49a99d19c853d43f0b8", 237 | "shasum": "" 238 | }, 239 | "require": { 240 | "php": ">=5.3.9" 241 | }, 242 | "require-dev": { 243 | "symfony/phpunit-bridge": "~2.7" 244 | }, 245 | "type": "library", 246 | "extra": { 247 | "branch-alias": { 248 | "dev-master": "2.7-dev" 249 | } 250 | }, 251 | "autoload": { 252 | "psr-4": { 253 | "Symfony\\Component\\Filesystem\\": "" 254 | } 255 | }, 256 | "notification-url": "https://packagist.org/downloads/", 257 | "license": [ 258 | "MIT" 259 | ], 260 | "authors": [ 261 | { 262 | "name": "Fabien Potencier", 263 | "email": "fabien@symfony.com" 264 | }, 265 | { 266 | "name": "Symfony Community", 267 | "homepage": "https://symfony.com/contributors" 268 | } 269 | ], 270 | "description": "Symfony Filesystem Component", 271 | "homepage": "https://symfony.com", 272 | "time": "2015-07-09 16:07:40" 273 | }, 274 | { 275 | "name": "symfony/yaml", 276 | "version": "v2.7.3", 277 | "source": { 278 | "type": "git", 279 | "url": "https://github.com/symfony/Yaml.git", 280 | "reference": "71340e996171474a53f3d29111d046be4ad8a0ff" 281 | }, 282 | "dist": { 283 | "type": "zip", 284 | "url": "https://api.github.com/repos/symfony/Yaml/zipball/71340e996171474a53f3d29111d046be4ad8a0ff", 285 | "reference": "71340e996171474a53f3d29111d046be4ad8a0ff", 286 | "shasum": "" 287 | }, 288 | "require": { 289 | "php": ">=5.3.9" 290 | }, 291 | "require-dev": { 292 | "symfony/phpunit-bridge": "~2.7" 293 | }, 294 | "type": "library", 295 | "extra": { 296 | "branch-alias": { 297 | "dev-master": "2.7-dev" 298 | } 299 | }, 300 | "autoload": { 301 | "psr-4": { 302 | "Symfony\\Component\\Yaml\\": "" 303 | } 304 | }, 305 | "notification-url": "https://packagist.org/downloads/", 306 | "license": [ 307 | "MIT" 308 | ], 309 | "authors": [ 310 | { 311 | "name": "Fabien Potencier", 312 | "email": "fabien@symfony.com" 313 | }, 314 | { 315 | "name": "Symfony Community", 316 | "homepage": "https://symfony.com/contributors" 317 | } 318 | ], 319 | "description": "Symfony Yaml Component", 320 | "homepage": "https://symfony.com", 321 | "time": "2015-07-28 14:07:07" 322 | } 323 | ], 324 | "packages-dev": [], 325 | "aliases": [], 326 | "minimum-stability": "stable", 327 | "stability-flags": [], 328 | "prefer-stable": false, 329 | "prefer-lowest": false, 330 | "platform": [], 331 | "platform-dev": [] 332 | } 333 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/migrations/20150817080530_create_users_table.php: -------------------------------------------------------------------------------- 1 | table('users'); 10 | $table 11 | ->addColumn('email', 'string', ['null' => false]) 12 | ->addColumn('password', 'string', ['null' => false]) 13 | ->addIndex('email', ['unique' => true]) 14 | ->save(); 15 | } 16 | 17 | public function down() 18 | { 19 | $this->dropTable('users'); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/migrations/20150818081646_add_created_at_to_users_table.php: -------------------------------------------------------------------------------- 1 | table('users'); 10 | $table 11 | ->addColumn('created_at', 'datetime') 12 | ->save(); 13 | } 14 | 15 | public function down() 16 | { 17 | $table = $this->table('users'); 18 | $table 19 | ->removeColumn('created_at') 20 | ->update(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/migrations/20150831053347_add_username_to_users_table.php: -------------------------------------------------------------------------------- 1 | table('users'); 10 | $table 11 | ->addColumn('username', 'string') 12 | ->save(); 13 | } 14 | 15 | public function down() 16 | { 17 | $table = $this->table('users'); 18 | $table 19 | ->removeColumn('username') 20 | ->update(); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/migrations/20150831053404_add_unique_username_index_on_users_table.php: -------------------------------------------------------------------------------- 1 | table('users'); 10 | $table 11 | ->addIndex('username', ['unique' => true]) 12 | ->save(); 13 | } 14 | 15 | public function down() 16 | { 17 | $table = $this->table('users'); 18 | $table->removeIndex(['username']); 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/phinx.yml: -------------------------------------------------------------------------------- 1 | paths: 2 | migrations: %%PHINX_CONFIG_DIR%%/migrations 3 | 4 | environments: 5 | default_migration_table: phinxlog 6 | default_database: development 7 | 8 | development: 9 | adapter: mysql 10 | host: localhost 11 | name: database_migrations 12 | user: homestead 13 | pass: secret 14 | port: 3306 15 | charset: utf8 16 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/scripts/PhinxBatch.php: -------------------------------------------------------------------------------- 1 | db = new PDO("{$databaseConfig['adapter']}:host={$databaseConfig['host']};dbname={$databaseConfig['name']}", $databaseConfig['user'], $databaseConfig['pass']); 16 | } 17 | 18 | public function getLatestMigrationVersion() 19 | { 20 | $query = $this->db->query('SELECT version FROM phinxlog ORDER BY version DESC LIMIT 1'); 21 | $query->execute(); 22 | $row = $query->fetch(); 23 | 24 | return ($row) ? $row['version'] : 0; 25 | } 26 | 27 | public function getBatchesFromFile() 28 | { 29 | if (!file_exists($this->batchFilePath)) { 30 | file_put_contents($this->batchFilePath, 0); 31 | } 32 | 33 | return array_map(function ($batch) { 34 | return trim($batch); 35 | }, file($this->batchFilePath)); 36 | } 37 | 38 | public function getPreviousMigrationVersion() 39 | { 40 | $batches = $this->getBatchesFromFile(); 41 | 42 | return isset($batches[count($batches) - 2]) ? $batches[count($batches) - 2] : 0; 43 | } 44 | 45 | public function getLatestMigrationVersionFromFile() 46 | { 47 | $batches = $this->getBatchesFromFile(); 48 | 49 | return isset($batches[count($batches) - 1]) ? $batches[count($batches) - 1] : 0; 50 | } 51 | 52 | public function writeLatestMigrationVersion() 53 | { 54 | $latestVersion = $this->getLatestMigrationVersion(); 55 | $latestVersionFromFile = $this->getLatestMigrationVersionFromFile(); 56 | 57 | if ($latestVersion != $latestVersionFromFile) { 58 | file_put_contents($this->batchFilePath, "\n" . $latestVersion, FILE_APPEND); 59 | } 60 | } 61 | 62 | public function removePreviousMigrationVersion() 63 | { 64 | $batches = $this->getBatchesFromFile(); 65 | 66 | unset($batches[sizeof($batches) - 1]); 67 | 68 | $batches = count($batches) > 0 ? $batches : [0]; 69 | file_put_contents($this->batchFilePath, implode("\n", $batches)); 70 | } 71 | } 72 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/scripts/migrate.php: -------------------------------------------------------------------------------- 1 | writeLatestMigrationVersion(); 12 | } 13 | -------------------------------------------------------------------------------- /manuscript/code/chapter-8/scripts/rollback.php: -------------------------------------------------------------------------------- 1 | getPreviousMigrationVersion(); 7 | 8 | // Run Phinx 9 | system('php vendor/bin/phinx rollback -t ' . $previousMigrationVersion, $exitStatus); 10 | 11 | if ($exitStatus === 0) { 12 | $phinxBatch->removePreviousMigrationVersion(); 13 | } 14 | -------------------------------------------------------------------------------- /manuscript/images/title_page.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/modess/deploying-php-applications/a2412cb9cbda28172c3888530baaddb1f0b313bb/manuscript/images/title_page.jpg --------------------------------------------------------------------------------