Under the assumption that we are talking whole machines (whether physical or virtual) for the production environment (rather than, say Google App Engine), my best practice is thus:
Take the extra effort to create proper packaging for your software. For example with Java development targeted for RedHat, use/write Maven tools that create RPMs. For your database, for all changes and include them in the RPM and either have them apply automatically or create a small tool that applies them for you. Essentially, you are done when a server can be installed/upgraded with a single command. Yes, this means including config files tailored to that system.
This is a bit of extra work, but it is so worth it in the long run.
Once you have properly packaged your software, the question of dev/staging/demo servers isn't so important anymore, because you can always build another. And with proper installers, you know exactly what changes you need to do to a machine to get it working properly.
Don't bother with migrating virtual machines around: you would need to change lots of configuration parameters on migration anyway.
If you tell us a bit more of OS/dev tools concerned, I may have some more specific tips for you.
EDIT: PHP/MySQL. Assuming this is LAMP.
Create a deb/rpm with your PHP code that depends on the various Apache, mysql, mod_ssl, php packages that you need. Look at other PHP applications packages on that platform for inspiration. Typically, you don't want to have a package dependency on a database, because the db may be on a different box.
Your package should prolly contain two SQL scripts (that comes from your source repo): one that initializes a new database, including grant statements, loading stored procedures, creating indices, etc and one that loads some demo data that can be used on test systems to get up and running quickly. Also, successive versions of your packages may contain patch scripts that upgrade the database.
Most modern Linux distributions frown on overwriting files from other packages, so try to avoid it if possible.
EDIT2: How to build a Debian package from just a directory tree.
You need a directory tree that looks like the installation you want to make (lets say in a dir called build), and you need some control files:
controlfiles/control:
Package: coolapp
Version: 1.0.0-2
Architecture: i386
Maintainer: Skunkworks Dept <bittrance@example.com>
Depends: apache2 (>= 2.2.16)
Section: contrib/libs
Priority: optional
Description: My Cool App
It's going to revolutionize, yo!
controlfiles/conffiles:
etc/coolapp/settings.conf
Given these two, use this script called mkdeb:
#!/bin/bash
CONTROLDIR=$1
shift
SOURCEDIR=$1
shift
INCLUDES=$*
BUILD=/var/tmp/mkdeb
CONTROLFILE=$CONTROLDIR/control
PKGNAME=$(grep -E '^Package:' $CONTROLFILE | awk '{ print $2 }')
PKGVERSION=$(grep -E '^Version:' $CONTROLFILE | awk '{ print $2 }')
PKGARCH=$(grep -E '^Architecture:' $CONTROLFILE | awk '{ print $2 }')
FILENAME=$PWD/${PKGNAME}_${PKGVERSION}_${PKGARCH}.deb
rm -rf $BUILD && install -d $BUILD
tar -C $SOURCEDIR -zcf $BUILD/data.tar.gz $INCLUDES
# Proper debian packages have md5 sums for all their files
(cd $SOURCEDIR && find $INCLUDES -type f | xargs md5sum > $BUILD/md5sums)
metadata=
for f in control prerm postrm preinst postinst templates conffiles ; do
if [ -f $CONTROLDIR/$f ] ; then
cp $CONTROLDIR/$f $BUILD
chmod a+x $BUILD/$f
metadata="$metadata $f"
fi
done
# Metadata and stuff
tar -C $BUILD -zcf $BUILD/control.tar.gz md5sums $metadata
echo 2.0 > $BUILD/debian-binary
(cd $BUILD && ar rc $FILENAME debian-binary control.tar.gz data.tar.gz)
Like so:
mkdeb ./controlfiles ./build .