Quick Deploy Caveats0:00
I'd like to wrap up by talking about deployments. You might think to yourself, well, I'm using Laravel Forge and I have Quick Deploy enabled, I don't need to worry about deployments. And you know what, for 99% of use cases, Quick Deploy is absolutely fine. But there are some edge cases you'll want to be aware of. If you're running something like our Laravel Pint workflow file, any time the main branch is updated, you're going to suffer from duplicate deployments. Because what will happen is, your pull request is merged, and then when the pull request is merged, Forge says, oh, main has been updated, let's get in there and deploy. Laravel Pint, our workflow file here, is kicked off. It updates the code, it cleans it, it pushes it back into main.
Laravel Pint, our workflow file here, is kicked off. It updates the code, it cleans it, it pushes it back into main. And then Laravel Forge says, oh, main has been updated, let's go ahead and deploy. So you get a dual deployment taking place. Also, as your team continues to expand, and maybe you have stakeholders as well, you might find yourself wanting a separate staging environment. So here we would have our standard ci.laracast.com, which, by the way, is not a real website. And then you would have cistaging.laracast.com. And whilst you want to push to staging whenever you merge a PR, you might want manual control over when you actually end up pushing to production.
Creating Deploy Workflow1:20
And whilst you want to push to staging whenever you merge a PR, you might want manual control over when you actually end up pushing to production. So because of those caveats, I'm going to disable Quick Deploy, and I'll talk with you about how I would implement deployments using GitHub Actions. Let's make a start by creating a new workflow file. I'll call this deploy.yaml. We'll give it a name of deploy. I'm going to define a job with the ID of deploy, and I want to make sure that this runs on ubuntu-latest as our other workflows do. Then I'm going to define a single step, which I'll give the name of deploy to Forge.
and I want to make sure that this runs on Ubuntu latest as our other workflows do. Then I'm going to define a single step, which I'll give the name of deploy to Forge. If you're not using Forge, there'll be a slightly different way of doing this, but usually all of these different providers have some way of being able to pull from a Git repository. If you are using Forge, you can simply make a call request to the endpoint defined in your Forge dashboard. So I want to deploy to staging here. I'm going to scroll down a little bit to this deployment trigger URL. I'll copy this, and for now, I'm going to paste this in. So obviously we don't want to keep it in plain text here, but it gets us off the ground. Okay, the other thing I have to define is when this workflow executes,
Triggering From workflow_run2:29
So obviously we don't want to keep it in plain text here, but it gets us off the ground. Okay, the other thing I have to define is when this workflow executes, and there's a really cool trigger called workflow_run. This allows us to hook into the progress of another workflow file and then execute this workflow at some point. So first of all, we say which workflows are we actually interested in listening to? In our case, we want to listen to the Laravel Pint workflow, and then I want to say any time Laravel Pint is completed, so any time this workflow file finishes, run this deploy workflow. And that's all we have to do to get this off the ground.
so any time this workflow file finishes, run this deploy workflow. And that's all we have to do to get this off the ground. So let's push this up and see if we can see it working. In our actions panel, you can see that the Laravel Pint command has kicked off. I'm going to refresh this now, and because Laravel Pint is now complete, our deploy step is now in progress. Let's go ahead and click into that. We'll take a look at what's taking place. We set up the job, deploy to forge, and then the job completes. Now, it finished with a green success code,
We set up the job, deploy to forge, and then the job completes. Now, it finished with a green success code, so if I go back to our staging environment that I have set up here, we should hopefully be able to see a recent deployment that's taken place. Yeah, here we go. 27 seconds ago, we did indeed make a deployment. Beautiful. It works. So that workflow now fixes the double deploy issue we were seeing because code is now only pushed to staging after Laravel Pint completes.
Deploying via Releases3:58
So that workflow now fixes the double deploy issue we were seeing because code is now only pushed to staging after Laravel Pint completes. What if we want to also use that deploy script to push to production? I think GitHub's releases are a perfect way of managing this. So you can draft a new release. Let's give it a tag of maybe v1.1.0, and then you can generate the release notes. You go down here, and you publish that release. That, for me, would be a great way to mark something as ready to ship. How would you listen for that in the workflow file?
That, for me, would be a great way to mark something as ready to ship. How would you listen for that in the workflow file? Well, alongside workflow run, we can have a second event, which is release, and you'll want to listen for just a single type, which is when a release is published. Of course, if you think about it, that doesn't really solve the whole problem because it's going to make a request to this staging URL instead of the production URL that we're actually interested in. To solve this, I'm going to reach for a GitHub feature that's really cool called Environments.
Using GitHub Environments4:56
To solve this, I'm going to reach for a GitHub feature that's really cool called Environments. Inside your repository settings, you'll see this page here called Environments. Let's go ahead and create a new environment. I'll create one for production. We can configure it, and you'll see that you can actually set up an environment as protected, so you have to have a certain number of reviewers to be able to publish to an environment. You can add different rules, branch restrictions, and you can also add environment-specific secrets and variables.
You can add different rules, branch restrictions, and you can also add environment-specific secrets and variables, which we covered a couple of episodes ago. Now, I'm going to add an environment variable, and let's just call this the deploy URL. I'll then head to ci.laracast.com, and I'm going to scroll down to our deployment trigger URL. Let's copy that, and then I'll paste that as the variable value and click Add Variable. Okay, so now we have this environment set up called production.
and click Add Variable. Okay, so now we have this environment set up called production that contains our deploy URL. Let's also set up another environment called staging. We can go ahead and configure that one, and we'll add another environment variable called deploy URL. This time, we're going to need to come to our staging forge environment. I'll head down to the trigger, go ahead and copy it, and then we can paste that as the variable value. Just so we're on the same page,
and then we can paste that as the variable value. Just so we're on the same page, in GitHub, we have configured a production environment and a staging environment. Both of those environments have a deploy URL set up as a variable that we'll be able to access inside our workflow file. In our workflow file, we'll want to define the environment we want to run in. So that could be production, it could be staging, whatever environments you've set up on GitHub.
So that could be production, it could be staging, whatever environments you've set up on GitHub. In our case, we want to be a little more programmatic than that because if you think about it, if we executed this workflow from a workflow run, then we can assume we're in staging. But if it was executed from a release, we want to assume we're in production. So rather than hard code this value, I'm going to reach for GitHub syntax here.
So rather than hard code this value, I'm going to reach for GitHub syntax here. I'm going to grab the GitHub object, the event name in that object, and I'm going to check if it's equal to workflow_run. If it is equal to workflow_run, then I know that we want to target the staging environment. So I'll use a double && here, and then I'll simply output staging. Then I can use a double || as a form of ternary operator, and if that's the case, I know that we're inside the production environment.
and if that's the case, I know that we're inside the production environment. Because we've configured our environment, we now have access to the correct deploy URL depending on whether we're in staging or production. So to access it, I'm going to create an environment variable for deploy to forge. We'll say deploy_URL, and I'm going to set this equal to vars.deployURL. vars is an object that's made available to you by GitHub,
and I'm going to set this equal to vars.deployURL. vars is an object that's made available to you by GitHub, and it will contain any variables that you've defined in GitHub settings. In this case, deployURL. Now we can replace this hard-coded URL with the environment variable that we've just configured, deployURL. Let's go ahead, push this up, and then create a release to check that it works as expected. So because we pushed to the Main branch,
and then create a release to check that it works as expected. So because we pushed to the main branch, Laravel Pint has automatically kicked off. And if we click into here, we should see in just a few seconds this job complete, Pint's running, postCheckout code, complete job. Okay, that works as expected. Let's go back to actions because hopefully we would now expect to see the deploy step kick off. And here we go.
because hopefully we would now expect to see the deploy step kick off. And here we go. So the deploy step has kicked off because Laravel Pint has completed. And because of how we've configured our environment, we would expect a new deployment to staging. To make sure that's the case, we'll click actions, and down the left-hand side, you can go to deployments. You'll see we have two different environments that we set up inside our settings.
You'll see we have two different environments that we set up inside our settings. If we click staging, yep, sure enough, one minute ago, we deployed to the staging environment. And to be doubly sure that that's the case, you can go to forge, deployments. If you scroll down to the bottom, yep, one minute ago, we deployed to our staging server. So that works perfectly. What about production?
So that works perfectly. What about production? Well, let's create a new release. We'll go to code, releases. I'm going to draft a new release here. We'll create a tag, v1.1.0. Let's go ahead and generate the release notes automatically. And then I'll publish this release, which we would expect would kick off a new action. So let's go to actions.
which we would expect would kick off a new action. So let's go to actions. Here's v1.1.0 using deploy. Shouldn't take too long to run because it's literally making a call request. Yep, you can see it's already completed. If we go back to actions, deployments, and let's click production, you can see that this was deployed just now. And then finally, we'll go to forge, to ci.laricast.com, deployments.
you can see that this was deployed just now. And then finally, we'll go to forge, to ci.laricast.com, deployments. And down at the bottom, hopefully, yeah, 23 seconds ago, we made a deployment. Pretty powerful, hey? And that really is just scratching the surface of what's possible with deployments in GitHub Actions. Of course, every scenario is different. So I can't guess what your deployment pipeline will be, but that should have given you plenty of ideas.
Series Wrap-Up10:30
So I can't guess what your deployment pipeline will be, but that should have given you plenty of ideas for how you might implement something similar for your use case. And with that, we're wrapping up the series. That's essentially everything you need for GitHub Actions, continuous integration, and Laravel. Such a powerful combination to automate all of the boring stuff that you really don't want to care about whilst ensuring that you maintain a high level of code quality. I hope you've enjoyed.
whilst ensuring that you maintain a high level of code quality. I hope you've enjoyed. If you've got some cool examples of GitHub workflows that you've created, be sure to post a link to them in the comments below because I'd love to be able to see them myself, but I'd also like for the community to be able to take a look through. I'll see you all next time. Take care.
