I have a confession to make, I am terrible at remembering all the different deployment checks and publishing chores for each of my projects. It's embarrassing, I know. Now that the aire has been cleared, let's find a way to compensate for my shortcomings.
In my latest project, I'll set up a full-stack multi-environment deployment pipeline using GitHub Actions. This has been so incredibly useful, I decided to share some details about why I chose this configuration and the benefits of this approach:
But Why?

For my project, I needed a safe space to test code changes before they went live. Local development has it's place, but you know it can be a pain for applications hosted on multiple environments. A staging platform provides a valuable resource for stakeholder reviews, facilitating regular feedback and deeper collaboration. This setup allows me to push changes where they are needed, and automagically perform any steps required for each environment. I shake my head when I think about all the time I wasted doing this manually.
Laying the Pipeline
I organized my repository into separate branches to accommodate each environment: main for production and develop for staging. Don't forget, this is a full-stack app, with front and backend hosted on different environments. This pipeline uses two Sync-to-FTP actions with separate credentials to deploy both front and backend to their respective servers. If you've ever mistakenly pushed the wrong files to the wrong server, you understand how helpful this is.
To control each environment independently, we can use environment-specific configurations. My staging environment uses a separate database, different API keys, and its own settings. GitHub Actions repository secrets simplify automating anything that varies between environments, like feature flags and API endpoints.
Conditional job execution allowed workflows to run differently depending on the branch. Staging can run a full suite of tests, providing confidence in the stability of the codebase. Production only gets a quick smoke test. To make it easier to inspect and debug code in the browser console, I disabled minification and enabled sourcemaps for the front-end on the staging environment. On production, minification was enabled to optimize performance and debug logging disabled to prevent accidental leaking of sensitive user data.
Access Control
One key benefit of this approach is access control. By granting developers access to specific branches, they automatically gain the ability to trigger deployments to the corresponding environments. Instead of juggling individual logins or shared credentials for each environment's hosting platform, I could manage access at the repository branch level. This not only streamlined onboarding and offboarding but also significantly improved security.
Workflow Procedure
Workflows are triggered by pushes to the relevant branches. In my workflow, a push to develop triggers the staging deployment, and a push to main triggers the production deployment.
The front-end build process uses npm run build
which runs the front-end build process (which is defined in package.json, silly). On staging, we can specify separate configuration files, with the --config dev.config.js
flag to customize build process more precisely. This back-end build uses a generic composer install
action, which could be customized further.
Here's a more detailed snippet, tying it all together:
name: Deploy Main to LIVE FTP
on:
push:
branches:
- main
jobs:
FTP-Deploy-Action:
name: Deploy to LIVE Action
runs-on: ubuntu-latest
steps:
- name: Get latest code
uses: actions/checkout@v4
- name: Use Node.js 18
uses: actions/setup-node@v4
with:
node-version: '18'
- name: Build Front
run: |
npm install
npm run build
working-directory: ./front/
- name: Sync Front Files
uses: SamKirkland/[email protected]
with:
server: ##.###.##.###
username: frontuser
password: ${{ secrets.front_ftp_password }}
protocol: ftps
local-dir: ./front/
server-dir: /front/
- name: Setup PHP
uses: "shivammathur/setup-php@v2"
with:
php-version: "latest"
- name: Build Backend
uses: "ramsey/composer-install@v3"
with:
working-directory: ./back/
- name: Sync Back Files
uses: SamKirkland/[email protected]
with:
server: ##.###.##.###
username: backuser
password: ${{ secrets.back_ftp_password }}
protocol: ftps
local-dir: ./back/
server-dir: /back/
To be continued...
This multi-environment deployment pipeline has been working great. The simplified access control and the ability to customize build processes for each environment have made deployments easier and faster, freeing me up for other stuff. Because everything is baked into the pipeline, I don't need to remember all the minutiae and procedures required to safely publish projects that use this approach. There are endless ways this approach could be adapted to other projects, and I'm eager to explore what else these methods can accomplish.
Related Links
- Official Documentation: From Github
- Using secrets in GitHub Codespaces: From Github
Share This Post
If you found this post interesting, please consider sharing it to your social networks.