ProgrammingDevOpstcm5mo4012mo

How to make deploying your software less painful

How long does it take for you to deploy a new version of your software? 1 week, 2 hours, 5 minutes? Does it take more than one person? Is the process painful and time consuming, do you dread having to do it every time? It doesn't have to be that way! In this article I'd like to explain some strategies I have used in my job and on personal projects to make my deployments easy and migraine free.

Document Your Processes

Outline a concrete strategy for your team to follow, with clear documented guidelines on how to make changes to the repository. Define coding standards and/or use automated linters to enforce consistency between different developers. Make this documentation centralized, searchable, and easy to update when needed.

Having this kind of information documented is really important especially when bringing in new developers, working in areas of the codebase you don't typically, or re-visiting only code that hasn't been touched in long periods of time.

Consistency reduces potential surfaces of conflict, and allows changes to be merged together with reduced difficulty.

Work in Small Steps & Generate Immediate Feedback

Try and make your changes in small discrete steps. Make a change, verify some result of that change. Make another change, do a test to see if things break. Make a change, print out some information to check it's validity. Make a change, have something break, undo that last change. Make a change, great things aren't broken now. Make a change, everything is working as expected. Make a commit!

Use manual and automated testing, hot reloading, and other similar tools to get immediate feedback to your changes. If things break, you will be able to see right away something is wrong and be able to pinpoint the source of the issue more easily. Verify the results of your changes as you go, work step by step alongside your immediate feedback to work towards your final goal.

Don't make large sweeping changes to the codebase without checking that your changes are working along the way! You don't want to put yourself in a position where you have made lots of critical changes, only then to discover major new issue which you can't quite seem to fix, only to realize you have 25+ changed files in your working tree.

Commit frequently and give yourself checkpoints you can revert to if things go really wrong. Ideally you really only want as many uncommitted changes, as you are willing to revert entirely if something goes wrong.

Once you are satisfied with a group of commits, you can sync all of those changes to the rest of the team with the confidence that your code works. You can optionally squash all of those commits into a single commit to keep your commit history squeaky clean and concise.

Have an Automated Deployment Process

Whether you use a fully managed CI/CD deployment pipeline, GitHub Actions, or just a simple BASH script; it's important to develop someway that you can deploy your entire software package with a single action.

While getting to this point can sometimes be extremely frustrating and time consuming (who decided that YAML should be the default 🤢), so is having to deal with those same frustrations every single time you need to deploy your software.

You don't need to do this right way, I would actually advise against that. Deploy your software manually at first, you need to figure that out anyways, so no need to include additional complexity yet. However once you start deploying new changes more frequently, and you have the initial scaffold in place; it's time to setup an automated deployment process.

Once you have your automated deployment process complete and functional, it's as easy as deploying your changes with a single command/click.

Environment Branches

I like to use an environment based branching strategy: typically for me that is just development and production. Each branch has it's own separately deployable environment, which can operate entirely self sufficiently without any outside interactions.

Whether you do microservices, monolith or somewhere in between; that means having having a separately running instance of everything, for each environment, which can exist on it's own. No outside dependencies, or external services; everything needs to be accounted for.

Development

All of the changes get made here, everyone makes their commits to the same branch simultaneously. This is the same approach taken by trunk based development (or TBD). Any commit you make, when synced with other developers changes, should always be in a state where things will 'fail safely'. Handle any potential errors, display a helpful and descriptive message to the user, and fail gracefully to avoid frustrating other developers with any 'incomplete' functionality that you commit.

Working in this way forces you to make small iterative changes, and incentivizes you to frequently check that your changes still work alongside everyone else's existing changes.

How do you handle large sweeping changes that may sometimes take days or weeks?

Well firstly, it's best to avoid making changes in that way. Even when doing large sweeping refactorings, it's possible to make incremental changes towards your final goal. Secondly, use feature branches! Yes, using feature branches goes against the typical TBD propaganda, but sometimes they are necessary and are extremely helpful in avoiding 'merge loss' (when your work disappears after a merge because the wizard who controls git forgot to take is anti-psychotics today).

When working on a feature branch it should be fairly short lived, and you should regularly be merging (or rebasing if feeling cheeky) development into your feature branch to stay up to date with any new changes. Keeping your feature branch always up to date with the current changes on development allows you to avoid those situations where your changes have become so far removed from the current state of the codebase, that it becomes impossible to complete the final merge after the feature has been completed.

Use feature branches sparingly: I would only use a feature branch if the changes being introduced would noticeably affect another developers workflow. Change the color of a button: not a feature branch. Change the API of a core system: feature branch! Also good for reviewing new developers changes before they have become familiar with the codebase.

When syncing your changes to other developers, your automated deployment process should automatically trigger a new deployment to the development environment. This environment functions as basically a continually running integration test. You should be able to use the development version of your software at any time without issue. This is your proof of functionality before releasing changes to production. The goal is to have this environment be indistinguishable for the production environment if you were to release all of the existing changes right at that very instant.

Production

I don't allow committing directly to production (🤯 mind blowing I know). As a branch, it's sole purpose is to be the stable source of truth for the customer facing, deployed software product.

So how do changes get released then?

When it is time to deploy your changes to your customers, you create a pull request (PR) for it! This gives a chance for the entire team to review ALL of the changes that will be getting released. You can discuss any concerns, make adjustments and last minute fixes, test edge cases, etc.

Once the PR has been approved, your automated deployment process can automatically detect that, and trigger a new deployment to the production environment.

Hotfix

I have found it beneficial to have a hotfix branch sometimes when needing to make critical patches to something in production, but am not quite ready to (or capable of) releasing all of the changes present on development.

Generally how I recommend having it setup, is whenever changes are merged into production, hotfix is automatically merged into as well. It should be an exact replica at all times. If a critical fix needs to go out, then you can make those changes to hotfix, setup a PR, have everything reviewed, then merge those changes into production. Those changes then automatically get merged into development and everything is back into order.

Keep your environment configuration outside of your repository. Changes there should be inconsequential to the actual operation of your software. Read from environment variables which apply the specific differences necessary for each environment.

Avoid Developer Silos

While it may seems logical to have certain developers be an expert in a certain area of the codebase and take full responsibility for specific systems, it can become dangerous if you let that go to far. You can quickly find yourself in the position where only a single person understands how a core module of your codebase works, and they've just taken an extended (or permanent) leave of absence.

Giving developers the ability to impact many different parts of the codebase allows them to gain a more complete understanding of the entire system. They will be better able to make changes that align more appropriately with the rest of the codebase, which makes integration of different changes easier and less frustrating.

It's okay to have certain developers be more familiar with certain systems, but if they only ever work in a single place, you run the risk of them getting burnt out and feeling like their work is not appreciated.

Conclusion

You have made have noticed that some (or most) of these things don't directly relate to actually deploying your software. Well that's entirely intentional; in my career so far, I have found that most things that block/hinder the deployment of new software versions come from things entirely unrelated to the deployment process itself.

While obviously the processes I have described above will not work for every single team out there (every single team is unique and has different needs, constraints, and requirements), I do think that you will be able to lift at least one helpful thing that you might be able to incorporate into your team.

What are some things you have implemented into your teams processes that you feel like have significantly improved your ability to iterate and release new versions of your software?