Setting up a development environment that behaves like your production environment can be tricky. There's the phrase "works on my machine" and who can't relate but the closer your dev environment is to productio the better. I recommend using vagrant, docker, vscode remote and git to make this a little easier to deal with.
There are plenty of framework tailored deployment platforms and some other big name cloud hosting platforms. They offer a very managed experience which is great but comes at a cost. If you've chosen to host your app on a server or vps you'll first need to think about a) getting your app's code onto the server, b) get the server to run your app.
- Docker
- VS Code + VS Code Remote
- Git
Keep your development environment as close to the production environment as possible.
Keep your deployment as portable as possible.
Take advantage of environment variable separation where necessary.
Version Control the whole solution.
With interpreted languages like php and maybe javascript (though that is unlikely) your app may not need a build step. If that is the case for you your deployment process could look like this:
1. pull changes from git
2. run docker build (load appropriate runtime and install dependencies)
3. run docker up
If your app does need to be built you'll want to add that step to your docker image at step 2. You could even go as far as building your docker image on your local machine and transferring the built image to your vps. Running the docker image building on your vps could use quite a bit of resources which could impact the running services on the vps and potentially some downtime. However if you build the image locally and transfer it over you will minimize downtime.
1. pull changes from git
2. run docker build (load appropriate runtime, install dependencies, and build app)
3. run docker up
Example Docker image file
FROM node:alpine as BUILD
#install dependencies and build app
WORKDIR /build
COPY package.json ./
RUN npm install
COPY ./ ./
RUN npm run build
#copy build and run app
FROM node:alpine
WORKDIR /app
EXPOSE 3000
COPY --from=BUILD /build/.output /app
CMD ["node", "./server/index.mjs"]
For an even more advanced setup you could load a virtual machine matching your vps's operating system. Doing this for development helps to simulate and practice the process of connecting to a machine and issuing remote commands, after all this is what we will be doing on our server.
My digital ocean hosting uses Ubuntu 18 with docker and docker compose included. Well guess what?! Vagrant has an Ubuntu 18 image and a docker provisioner. This gets my development environment to almost 100% replicate my production environment.
In order to get the docker provisioner you will need to run this command to install it:
vagrant plugin install vagrant-docker-compose
Here is my vagrant file as an example:
Vagrant.configure("2") do |config|
config.vm.box = "ubuntu/bionic64"
config.vm.network "private_network", ip: "192.168.33.15"
config.vm.hostname = "laravel"
config.vm.synced_folder ".", "/var/www", :mount_options => ["dmode=777", "fmode=666"]
config.vm.provision :docker
config.vm.provision :docker_compose
end
Docker and docker-compose lets you build up your software from separate parts. Pick a web server, pick a web application framework, pick a database. You can keep each of these concerns separate, and connect them where needed. Want to change database technologies? Just swap out that docker image and migrate your data and the other images can remain untouched. And the beauty of it all is its super easy to migrate between servers.
Another great feature of docker-compose is configuration file overrides. For the situations where you require different configurations between your environment you can create separate docker overrides to extend the base configuration.
Example docker compose file
version: '3'
services:
#PHP Service
app:
build:
context: .
dockerfile: Dockerfile
working_dir: /var/www
volumes:
- ./:/var/www
networks:
- app-network
#Nginx Service
nginx:
image: nginx:alpine
container_name: nginx
restart: unless-stopped
tty: true
volumes:
- ./nginx/conf.d/:/etc/nginx/conf.d/
depends_on:
- app
networks:
- app-network
- net
command: "/bin/sh -c 'while :; do sleep 6h & wait $${!}; nginx -s reload; done & nginx -g \"daemon off;\"'"
#DB Service
db:
image: postgres:alpine
container_name: db
restart: unless-stopped
tty: true
volumes:
- dbdata:/var/lib/postgresql
networks:
- app-network
#Docker Networks
networks:
net:
external: true
app-network:
driver: bridge
#Volumes
volumes:
dbdata:
driver: local
I recommend creating a branch for your production release. You should merge your changes from any feature branches to main and when you are satisfied with all your changes and want to deploy them merge them back to the release branch.
Version control is such a powerful tool and Github makes so many of these features so accessible and intuitive. Using pull requests and code review stages are great development practices and are so easy with GitHub's web interface.
This is an extension for vscode that lets you connect vscode to your remote server as if it is local. You get your familiar project window but you have real-time control over your server.
In a perfect world we could publish changes with the push of one button and everything would just work however this is often not the case (Well it is if you pay for a managed deployment service instead of a vps). Commands need to be run, packages downloaded, databases migrated, configuration files tweaked. VS Code Remote makes this a breeze. No longer do you feel like you're flying blind; pushing an update to the live site and to have no clue whats going wrong.
Another VS Code recommendation I would make is customizing the color theme on your release branch. Using Vs Codes workspace settings you can create custom title bars for production environment so you never get confused with which code server you are working in.
Production Settings:
{
"workbench.colorCustomizations": {
"statusBar.background": "#00AA00",
"statusBar.foreground": "#FFFFFF",
"titleBar.activeBackground": "#00AA00",
"titleBar.activeForeground": "#FFFFFF"
}
}
Transitioning from development to production and juggling these two environments can be difficult. These are the tools and setup I can recommend that are working for me.