DANIEL
STOKES

Setting up a development environment for VPS deployment

November 2, 2024 at 12:30 AM

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.

Tools I recommend:

- Docker

- VS Code + VS Code Remote

- Git

Summary

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.

Deployment Strategy

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

Dockerfile
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"]

Vagrant

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:

Vagrantfile
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

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

docker-compose.yml
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

Version Control

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.

VS Code & VS Code Remote

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:

.vscode/settings.json
{
	"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.

© Daniel Stokes 2024