Great post on slashdot about setting up a proper environment web developing. I’m posting a good one here for future reference. Enjoy.
This is Unix/OSX-centric, and I’ve been doing this for years and showing others the way:
* My code always has a standard layout (bin, conf, src, lib, and so on). No exceptions, because you never know when that little script will become a big app (this happens to me at least once a year).
* Use good coding practices: unit tests, continuous integration, whatever
* The code is checked into CVS/Subversion/Darcs, whatever. Use branches and so forth intelligently (dev on the trunk, release branches which are bugfix only, whatever). Make it “obvious” where the latest stable code always lives, so that someone besides yourself can deploy it.
* I have a script which will deploy the app to any server with rsync (excluding CVS, config, test, and dev files). There is also a flag that will “pull” the files from the server, in case of an emergency fix that was done directly on the server.
* There’s a “config” directory with all system-dependent configs. No passwords or other stuff is hard-wired into the app.
* As others have described, you have your various “dev” machines, a “staging” server (identical to production but non-critical), and a “production” server. NEVER WORK ON THE PRODUCTION SERVER EXCEPT IN EMERGENCIES. Also, resist the temptation to install extra stuff on the staging server like some do (MRTG or Nagios or whatever). The staging server must be identical to the production in all respects.
If you do this properly, all you have to do to work is the following:
1) go to your laptop or other dev environment and check out the code.
2) review the REQUIREMENTS file for any packages you might need to install
3) adjust the app config files appropriately
3) code, test, checkin, code, test, checkin, …..
Then when you’re ready to update the code on the server, first sync to the staging server and test as needed (user tests, whatever). Virtual hosts can work this way too (I’ve done it this way, for instance “foo.com” is the app and “dev.foo.com” is the staging version, on the same machine).
Once everything is working, push to the production servers (my script will also restart anything that’s needed .. by the way, use daemontools to supervise your daemons, anything else like crappy pidfile-based startup scripts are unpredictable). Ideally someone OTHER THAN YOURSELF should be able to mindlessly follow some simple instructions to accomplish this step. Design your procedures with this in mind.
I also agree with the poster above who said it’s good to have different OS or environments on your dev machines, to catch any hidden assumptions. I dev on Linux and OSX and push to FreeBSD usually. Use conditional code as appropriate, and sparingly.
Ideally, you can take a blank server, install the OS, install the REQUIREMENTs, push the app, config the app, run a setup script, and go. No undocumented requirements. No weird “procedures”. Just “push button install”.
You should also get into the habit of making apps “learn” as much about their environment as possible. For instance, in my Ruby, Perl, PHP code, I use the __FILE__ variable or equivalent to determine the install dir, that way I don’t have to configure it. A common library sets up all the necessary paths based on that.
Write your code to be flexible and backwards-compatible. For instance, if you need to move some data files around or change database fields, write code that detects the old version and does the update at startup time. A little extra work, but oh-so-automatic.
Once you get this working, you’ll never want to work any other way. Being able to check out and deploy your code ANYWHERE in just a few steps is a very powerful feeling. Heck, just being able to check out in a different directory on your dev machine is useful. Having separate release branches are awesome when the client reports a bug but you’ve already started the big changes for 2.0 .. OR when you get a SECOND client who will run an older version of the code and you have to maintain both. *Assume* your code will be popular and successful, think big from the start .. no throwaway scripts allowed, do things right from the get-go.
And if you’re inheriting a system that doesn’t work this way, refactor it until it’s like this. Just make small changes regularly. I inherited an UTTERLY CRAPTACULAR PHP system and over the course of a year, hammered into shape while doing the work the client requested. Now everything is laid out beautifully, there’s a test framework, it can be deployed with one command, all dependencies are documented, etc.