{"id":3318,"date":"2025-12-14T17:14:00","date_gmt":"2025-12-14T17:14:00","guid":{"rendered":"https:\/\/www.antpace.com\/blog\/?p=3318"},"modified":"2026-02-14T17:15:08","modified_gmt":"2026-02-14T17:15:08","slug":"auto-deploy-your-site-with-github-actions-goodbye-filezilla","status":"publish","type":"post","link":"https:\/\/www.antpace.com\/blog\/auto-deploy-your-site-with-github-actions-goodbye-filezilla\/","title":{"rendered":"Auto-Deploy Your Site with GitHub Actions"},"content":{"rendered":"\n<p>My original deploy flow was the classic \u201cfreelancer special\u201d:<\/p>\n\n\n\n<p>Make changes locally, open FileZilla, SFTP into the server, drag files over, hope I didn\u2019t miss anything, refresh the site, repeat.<\/p>\n\n\n\n<p>It worked, but it was slow and it always felt a little fragile. So we switched to auto-deploys with GitHub Actions. Now whenever I push to <code>main<\/code>, GitHub connects to the server and runs a deploy script automatically.<\/p>\n\n\n\n<p>Push code \u2192 live site updates. No more FileZilla sessions.<\/p>\n\n\n\n<p>And here\u2019s a bonus I didn\u2019t fully appreciate until I started using it: it also makes it ridiculously easy to make small static page edits from my phone using the GitHub.com editor. Change a line of HTML, fix a typo, update a link, commit to <code>main<\/code>, and it deploys the same way as if I did it from my laptop.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">What We Set Up<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>Trigger:<\/strong> A push to <code>main<\/code> runs the workflow.<\/li>\n\n\n\n<li><strong>Action:<\/strong> The workflow SSHs into the server (in our case, an AWS Lightsail instance), goes to the site\u2019s document root, and runs a small deploy script.<\/li>\n\n\n\n<li><strong>Result:<\/strong> Every push to <code>main<\/code> updates the live site without manual SFTP, SSH, or FTP.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">1. The GitHub Workflow<\/h2>\n\n\n\n<p>Create <code>.github\/workflows\/deploy.yml<\/code> in your repo:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>name: Deploy to Lightsail\n\non:\n  push:\n    branches: &#91; main ]\n\njobs:\n  deploy:\n    runs-on: ubuntu-latest\n    steps:\n      - name: SSH deploy\n        uses: appleboy\/ssh-action@v1.0.3\n        with:\n          host: ${{ secrets.HOST }}\n          username: ${{ secrets.USERNAME }}\n          key: ${{ secrets.SSH_KEY }}\n          port: ${{ secrets.PORT || 22 }}\n          script: |\n            cd \/opt\/bitnami\/apache\/htdocs\n            .\/scripts\/deploy.sh\n<\/code><\/pre>\n\n\n\n<p>What\u2019s happening here:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>appleboy\/ssh-action<\/code><\/strong> runs the given script on your server over SSH.<\/li>\n\n\n\n<li><code>host<\/code>, <code>username<\/code>, <code>key<\/code>, and optionally <code>port<\/code> come from GitHub <strong>Secrets<\/strong> so you never put credentials in the repo.<\/li>\n\n\n\n<li>Adjust the <code>cd<\/code> path to your actual document root.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">2. The Deploy Script on the Server<\/h2>\n\n\n\n<p>On the server, we keep the deploy logic in a script (e.g. <code>scripts\/deploy.sh<\/code>) that the workflow runs:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>#!\/bin\/bash\nset -euo pipefail\ngit pull origin main\n<\/code><\/pre>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>set -euo pipefail<\/code> makes the script exit on errors and on use of unset variables.<\/li>\n\n\n\n<li>The only \u201cdeploy\u201d step is pulling the latest <code>main<\/code>. You can expand this later (build steps, dependency installs, cache clears, service restarts, etc.).<\/li>\n<\/ul>\n\n\n\n<p>Make sure:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>The script is executable: <code>chmod +x scripts\/deploy.sh<\/code><\/li>\n\n\n\n<li>The directory you\u2019re deploying from is a git clone of your repo, with <code>origin<\/code> pointing to the same GitHub repo you push to.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">3. GitHub Secrets<\/h2>\n\n\n\n<p>In your repo: <strong>Settings \u2192 Secrets and variables \u2192 Actions<\/strong>. Add:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>HOST<\/strong> \u2013 Your server\u2019s hostname or IP (example: <code>origin.antpace.com<\/code>)<\/li>\n\n\n\n<li><strong>USERNAME<\/strong> \u2013 SSH user (Bitnami stacks often use <code>bitnami<\/code>)<\/li>\n\n\n\n<li><strong>SSH_KEY<\/strong> \u2013 Private key content that can log in as that user (paste the whole key including the BEGIN\/END lines)<\/li>\n\n\n\n<li><strong>PORT<\/strong> (optional) \u2013 SSH port if not 22<\/li>\n<\/ul>\n\n\n\n<p>Now the runner can SSH in and deploy without you storing credentials in the repo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">4. The Underrated Win: Editing From Your Phone<\/h2>\n\n\n\n<p>This is the part that feels almost unfair once you have it.<\/p>\n\n\n\n<p>Because deploys are tied to <code>main<\/code>, you can make simple static changes directly in the GitHub.com editor on mobile:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Fix a typo<\/li>\n\n\n\n<li>Update a phone number<\/li>\n\n\n\n<li>Swap a link<\/li>\n\n\n\n<li>Change a headline<\/li>\n\n\n\n<li>Add a quick announcement banner<\/li>\n<\/ul>\n\n\n\n<p>Commit to <code>main<\/code>, and the same workflow runs. No laptop required. No FileZilla. No \u201cI\u2019ll do it later when I\u2019m home.\u201d<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">5. One More Thing: Cache Invalidation<\/h2>\n\n\n\n<p>If you sit behind a CDN (like CloudFront), you may need to invalidate cache after deploy so you actually see your changes immediately.<\/p>\n\n\n\n<p>You can:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Add a workflow step that uses the AWS CLI to create a CloudFront invalidation, or<\/li>\n\n\n\n<li>Run it inside the same SSH script (if the AWS CLI is installed on the server)<\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<p>That\u2019s it.<\/p>\n\n\n\n<p>The whole goal here is to stop \u201cdeploying\u201d and start \u201cpushing.\u201d Once this is in place, you\u2019re not moving files around anymore. You\u2019re making changes in one place (your repo), and production stays in sync automatically.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>My original deploy flow was the classic \u201cfreelancer special\u201d: Make changes locally, open FileZilla, SFTP into the server, drag files over, hope I didn\u2019t miss anything, refresh the site, repeat. It worked, but it was slow and it always felt a little fragile. So we switched to auto-deploys with GitHub Actions. Now whenever I push &hellip; <\/p>\n<p class=\"link-more\"><a href=\"https:\/\/www.antpace.com\/blog\/auto-deploy-your-site-with-github-actions-goodbye-filezilla\/\" class=\"more-link\">Continue reading<span class=\"screen-reader-text\"> &#8220;Auto-Deploy Your Site with GitHub Actions&#8221;<\/span><\/a><\/p>\n","protected":false},"author":1,"featured_media":3321,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[4,5],"tags":[],"class_list":["post-3318","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-technology","category-web-development"],"_links":{"self":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/3318","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/comments?post=3318"}],"version-history":[{"count":3,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/3318\/revisions"}],"predecessor-version":[{"id":3322,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/posts\/3318\/revisions\/3322"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media\/3321"}],"wp:attachment":[{"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/media?parent=3318"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/categories?post=3318"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.antpace.com\/blog\/wp-json\/wp\/v2\/tags?post=3318"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}