I'm attempting to simplify and automate the development, testing, and production deployment of web applications for my small business. I have a lot of ideas in mind how I ideally want it to function, however I don't want to re-invent the wheel. I could spend weeks or even months coding a system that works exactly how I want it, but if the proper tool already exists I'm willing to pay for it.
What I have set up right now may sound a bit low-tech or simple, but remember this is just my first pass at setting up a development pipeline. There's still a lot of improvement that can be made.
Below I describe my current setup, the issues I've faced with this setup, and an explanation of where I stand in resolving these issues. What I'm looking for (if they exist) are tools that will simplify, automate, or otherwise handle these issues I'm facing.
What I have now
Currently I track all code in BitBucket. There is one repository per web site or web application that I create.
When a new feature request comes in or when a bug is discovered, a ticket is created in JIRA.
Within the BitBucket repository the "master" branch is considered to be always deployed to production. When a new feature or bug is to be worked on, a branch is created (per ticket) off of master. For extremely large changes (like switching from Angular to React) for which I create an epic in JIRA I will create one branch for the epic, then create branches (per ticket) off of this epic branch.
Development can technically be done anywhere (the repository can be cloned to a local machine if desired), although for development we generally use Cloud9. This way we can just click "Clone to edit" and start working on code.
Merging into master is only allowed via pull request.
Once a pull request is created, we check out the branch in staging and then merge master into the branch being tested to verify the new feature or bug fix (and do some quick smoke testing for regressions). By merging master into the branch being tested, we're not just testing the feature branch - but how it interacts with the code that's on master. Any bugs that arise are therefore quarantined to this branch until they're fixed.
After code is merged into master, the master branch is pulled in production and any build scripts (generally just
gulp) are run.
Benefits to current solution
It was extremely easy to teach this pipeline to a new developer. I hired a 1099 contractor at one point to help me with a large web application project I had and he was able to get up to speed and start writing code in a single day
The creation of pull requests forces us to review the code that has been changed and reminds us to test everything thoroughly
By merging master into the feature branch before merging the feature branch into master we detect merge conflicts and bugs early
A combination of separate branches per ticket and the "master -> feature -> master" merge method allows us to deploy tickets in any order. We could have 10 features in development when a critical bug is found. We can get that critical bug fixed and deployed without waiting for the 10 features to be complete
Problems with current solution
Environments are not guaranteed to be standardized. We've had issues where something worked in dev and staging then upon pushing to production we found the MySQL version was out of date
Deploying to staging and production are both manual processes (log into the staging server via SSH, check out the branch, run build scripts, log out)
The "build" step is not always very fast, and during this production is generally offline. We would like to move "build" to a separate (post-dev but pre-staging) step in the deployment pipeline
There's no automated testing (no unit tests, no continuous integration, etc)
I'm not sure if BitBucket + JIRA + Cloud9 can solve all of these problems and I'm just using them poorly, or if replacing one of these tools (perhaps using CodeAnywhere instead of Cloud9, or using GitHub instead of BitBucket) would help. There may also be a fourth tool which I should integrate into my development process that could hammer out these details for me.
I know that using containers can solve the majority of my problems here. Using Docker I can ensure a consistent environment across development, staging, and production. However all of my experience with Docker so far has involved manually spinning up and down containers. I like how in Cloud9 I can click "Clone to edit" and start working. I want this same kind of simplicity with Docker. Is there a way to use BitBucket+JIRA+Cloud9 and say "work on this repo" and have it spin up all of the containers defined in a
docker-compose.yml file? Is there some other tool that could make this process simple?
Concern: I would like to start using Docker if possible to make all of my environment consistency headaches go away, but I don't want to add complexity to my process. I like being able to click "clone to edit" and get to work. If I had to spin up a new VM, configure an SSH workspace on Cloud9, then run
docker-composer up -d every time I would likely forget a step or start avoiding creating new projects due to the overhead.
I believe that something like BitBucket Pipelines can help me here, but... I have no idea how to use it. My belief may even be wrong. Do I need to use a tool like Travis for this? I've seen Travis with a lot of open source projects, but I don't really understand what it is or how it works.
Concern: While I don't want to have to log into the staging and production servers and manually run a bunch of commands in order to deploy code, I also don't want code slipping into staging or production by accident. I want to explicitly click on a branch and say "put this branch in staging", and when that branch gets merged to master (via pull request) is the only time it should be deployed to production.
Most of the work that I do involves PHP web sites and web applications. This means there are generally three "build" commands:
That last step I'm hoping to standardize eventually to
npm run build because I don't think the particular choice of build tool should impact the build steps.
The problem with these three steps is that they can take a short while to run. Usually not too long, but I've seen an
npm install take 12 minutes before.
Because I currently have no containerization and manual environment management, this hasn't been a huge problem for me. Because when I log into production and run
git pull, all of the Composer packages and NPM packages are still installed from the last deployment -- so I just run
git pull I'm done.
I know that this isn't ideal, though. I have never had to work on a project which garnered so much traffic it required multiple servers with load balancing (my largest production server is a single VM with 4 gigs of RAM and it usually runs at 5% capacity with spikes to 60% rarely). However if I ever do build an app that gets millions of viewers daily, I'll likely want to move towards distributed architecture.
Whether I use Docker or not, spinning up additional servers to handle load means spinning up additional environments -- and that means all dependencies must be installed again.
Concern: I don't want this build step to take too long. If running
composer update && npm install && gulp takes 12 minutes, I don't want to wait 12 minutes every time I'm ready to test some code in staging. Ideally the build server will function similar to my current staging and production servers -- where dependencies I have previously downloaded don't need to be downloaded again
Second concern: I want this build step to be automatic when a pull request is created
This is an area where I have a lot of theoretical knowledge and zero practical experience. I've never worked at a company that did good unit testing and I've never written a project with good unit tests. In some cases I just don't know how to write unit tests for certain functionality (like how does one write a unit test to ensure all text is legible in a UI?)
Where unit tests can be written, though, I'd like to start moving towards test-driven development. In the two years I've been running my small business I've made 11 sites / apps and in all of that time I've had only a single regression -- but I still think even that one was unacceptable. Code that works shouldn't magically stop working.
As with everything else, I don't know if BitBucket, JIRA, or Cloud9 has a feature that can help me here or if I'll need to pull in some new tool. In an ideal world, we want to catch regressions as early as possible -- and therefore I would argue that you wouldn't even be able to create a commit if unit tests fail. Obviously the later in the process you run these tests, the less motivated people are to fix them. If someone has added a feature, smoke tested the code, pushed to BitBucket, created a pull request, and it went through staging and then we threw up a red flag saying "before going to production, we found this unit test failed" -- that would just be frustrating.
Concern: As with the build step, this needs to be fast. If there is a way to prevent code from being committed if unit tests fail (and I don't know that there is, since a developer can use any IDE of their choice) then this needs to be extremely fast - otherwise every time someone tries to save their work they're sitting around and waiting several minutes.
Second concern: This must be automatic. I don't want to depend on a human to click the "test" button. Whether it's when you commit, when you push changes to BitBucket, when you create a pull request, or when that pull request is merged -- regardless of when the tests are run, they must be run implicitly by some standard action.