This article is also available in on
Let's take a look at how this workflow is built.
Building the Workflow
The workflow lives at
.github/workflows/deploy.yml. Here's the basic structure:
CODEname: Deploy Production
on:
workflow_dispatch:
inputs:
tag:
description: "Git tag to deploy (example: v1.2.3)"
required: true
type: string
run_migration:
description: "Run database migration?"
required: false
type: boolean
default: false
With the
workflow_dispatchconfiguration above, GitHub will display a simple form every time we want to run the workflow. There are two inputs:
tag, i.e. the Git tag to deploy, required
run_migration, i.e. a checkbox to run database migrations, optional
Pretty straightforward. Now let's dive into the
jobssection.
SSH into the Server
The first step is setting up an SSH connection to the Lightsail server using an SSH key stored as a GitHub Secret:
CODEjobs:
deploy:
runs-on: ubuntu-latest
steps:
- name: Setup SSH
run: |
mkdir -p ~/.ssh
echo "${{ secrets.SSH_PRIVATE_KEY }}" > ~/.ssh/id_ed25519
chmod 600 ~/.ssh/id_ed25519
ssh-keyscan -H ${{ secrets.SSH_HOST }} >> ~/.ssh/known_hosts
Here we're saving the private key to
id_ed25519with permission600(readable only by the owner). Then we add the server's host key toknown_hostsso the SSH connection isn't rejected.
Keep in mind, all sensitive information like
SSH_PRIVATE_KEY,SSH_HOST,SSH_USER, andAPP_PATHshould be stored as GitHub Secrets. Never hardcoded in the workflow file.
Checkout Tag and Deploy
Once the SSH connection is ready, we send commands to the server to fetch tags, checkout, and build with Docker:
CODE- name: Deploy to Server
run: |
ssh ${{ secrets.SSH_USER }}@${{ secrets.SSH_HOST }} "export GITHUB_PAT='${{ secrets.CREDENTIAL_GITHUB_PAT }}' && bash -s" << 'EOF'
set -e
set +o history
cd ${{ secrets.APP_PATH }}
echo "Temporarily set Git remote with token"
ORIGINAL_URL=$(git remote get-url origin)
REPO_PATH=${ORIGINAL_URL#https://github.com/}
git config --local credential.helper '!f() { echo "username=x-access-token"; echo "password=${GITHUB_PAT}"; }; f'
echo "Fetch tags"
git fetch --tags
echo "Checkout tag: ${{ github.event.inputs.tag }}"
git checkout -f ${{ github.event.inputs.tag }}
echo "Remove credential helper"
git config --local --unset credential.helper
echo "Deploy with Docker Compose"
export USER_ID=$(id -u)
export GROUP_ID=$(id -g)
docker compose up -d --build
set -o history
echo "Deployment completed successfully!"
EOF
There are a few interesting things here. Let's break them down.
First, we're using a heredoc (
<< 'EOF') to send a block of shell commands to the server over SSH. This is much cleaner than writing everything inline in one long line.
Second, we're using a Git credential helper instead of storing the token in the remote URL. This is more secure because the credential helper is removed right after
git checkoutcompletes. The token only lives for the duration of that command.
Third, notice
set +o historyat the top. This disables bash history temporarily so the GitHub token doesn't get recorded in the server's shell history. A small security detail that's often overlooked.
Fourth,
export USER_IDandGROUP_IDare needed so the Docker container runs with the same user ID as the host, keeping file permissions consistent.
Running Migrations
The final piece is the option to run database migrations:
CODE# Run migration if requested
if [ "${{ github.event.inputs.run_migration }}" == "true" ]; then
echo "Running database migration..."
docker exec container_app php artisan migrate --force
echo "Migration completed!"
else
echo "Skipping migration"
fi
Pretty straightforward. Check if the
run_migrationinput istrue. If yes, runphp artisan migrate --forceinside thecontainer_appcontainer. The--forceflag is needed because Artisan usually prompts for confirmation in production environments.
This part used to be the most annoying. Often, right after
docker compose up -dfinished, I had to quicklydocker execto run migrations before any requests hit the new tables. Now it's just ticking a box and we're done.
The Final Result
With the workflow ready, deploying now is as simple as:
Push commits and create a tag (git tag v1.2.3 && git push origin v1.2.3)
- Open the repository on GitHub, Actions tab → select Deploy Production → click Run workflow
- Enter the tag you want to deploy
- Check
Run database migration?if there are database changes
- Click Run workflow and wait for the success notification
Here's the before-and-after comparison:
Step
Before (Manual)
After (GitHub Actions)
Checkout code on server
SSH → git fetch→git checkout
Automated via workflow
Build & deploy
docker compose up -d --build
Automated via workflow
Database migration
SSH → docker exec→php artisan migrate
Check a checkbox
Total time
2–3 minutes
~40 seconds (input params)
Human error risk
High (missed steps)
Low (standardized)
From 2-3 minutes down to ~40 seconds — that's roughly an 80% reduction in deployment time. But more important than the time savings is this: deployments are now consistent and free from human error.
Conclusion
Migrating from Beanstalk to Lightsail definitely pushed me out of my backend engineer comfort zone and into infrastructure territory I'd previously left to automated services, deployment included. But that's exactly where the learning happens.
With GitHub Actions
workflow_dispatch, we can build a simple CI/CD pipeline that's just as convenient as managed services like Beanstalk. All you really need is:
- A repository on GitHub
- A server accessible via SSH
- An application containerized with Docker
- A single workflow YAML file
Sure, this pipeline may not be as sophisticated as Jenkins or GitLab CI with automatic rollback and approval gates. But for small teams or personal projects, it's more than enough.
If you have any additions or corrections to the discussion above, let's talk in the comments. Hope this helps 👋.
↗ Original-Artikel auf dev.to lesenVollständiger Original-BerichtAusführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
Ähnliche Beiträge
Auch interessante Nachrichten Cutting Deployment Time by 80% with GitHub Actions and Docker
Thematisch verwandte Begriffe: Cutting, Deployment, Time, with · 6 Treffer
The Gemini desktop app is now available for Windows
Burn Out, Or Fade Away
Windows 11 KB5129195 is out after Microsoft confirms major issues with the September 2026 update, but it won’t fix AMD GPU errors
Amazon Prime Big Deal Days: October 6 to October 7, 2026
Vorsicht: Ihr Smart-TV kann Gespräche mithören, selbst wenn er ausgeschaltet ist
Spotify
Videos werden geladen ...
Beiträge werden geladen ...
Videos werden geladen ...
Beiträge werden geladen ...
Videos werden geladen ...
Beiträge werden geladen ...
Videos werden geladen ...
Beiträge werden geladen ...
Videos werden geladen ...
SOCIAL SHARE CARD GENERATOR