Add Github Action Workflow and Secrets

Self hosted PayloadCMS and PostgreSQL website on Docker

5 min read

Published Jun 17 2025, updated Jun 19 2025


10
0
0
0

CaddyDockerGitHub ActionsJavascriptNextJSPayloadCMSPortainerTailscaleUbuntuUFW

Now we will setup a GitHub Action workflow so that whenever the code is pushed to the main brnach, we will automatically build the docker file, push it to our registry, and ultimately update the website service automatically.


Obtain Tailscale auth key

We require the GitHub action to be able to connect over our Tailscale network to be able to run the database migrations and to SSH in to the server to update the service. So we will create an ephemeral, reusable key that can spin up a Tailscale instance on our network, and then at the end as its ephemeral, it will automatically delete its self a few minutes after the GitHub Action finsihes.


Log in to Tailscale.com admin console.

Go to the 'Settings' tab and select 'Keys' on the left menu and click 'Generate Key' button.

Give the key a name, select 'Reusable' and 'Ephemeral' and click generate key.


Setup GitHub Action secrets

We will setup some secure secrets that can be fed in to the action and build process when the GitHub action runs.

Both access to the db over Tailscale and the Payload Secret are required for the building process.

NEXT_PUBLIC_ prefixed env variables that are available on the client side get built in to the Javascript as part of the build process, so need to be included in the image directly. For my case where i'm only ever creating a single production image so the public env variables don't change, its ok to build in to the image. However, if you require a generic docker image that can be deployed with different configurations then don't use NEXT_PUBLIC env variables, either pass the values down to client components as properties from normal env variables, or create an API end point to pass down client side values. Setting NEXT_PUBLIC env variables in Docker at service launch time will result in undefined client side reads of the variables. So I create. single secret for all the public envs and then in the GitHub Action work flow i write them to a .env file and include directly in the image so they get included as part of the build.


Now log in to GitHub and go to your websites repository.


Click the 'Settings' tab at the top.


On the left menu expand the 'Secrets and variables' menu item and select 'Actions'.


Click the 'New repository secret' button and set the value for each of the following:


Secret

Description

TAILSCALE_AUTH_KEY

The Tailscale auth key obtained above

DATABASE_URI_PRODUCTION

Database connection string production env variable but with the Tailscale IP address

PAYLOAD_SECRET_PRODUCTION

Payload secret production env variable

NEXT_PUBLIC_ENV_PRODUCTION

All your NEXT_PUBLIC_ env variables listed under each other with production values

REGISTRY_PASSWORD_PRODUCTION

The access password of your private docker registry you set before

REGISTRY_USER_PRODUCTION

The access user of your private docker registry you set before

HOST_PRODUCTION

Tailscale IP of your server

USERNAME_PRODUCTION

Username of your server

PASSWORD_PRODUCTION

Password of your server


Creating the Workflow

This is the GitHub action code that you want to run when the code is pushed to the main branch automatically on GitHub.


In your website code, in the root of the project, create a folder called .github (notice the . at the start), and add a child folder called workflows.


Create a new file in that folder called something like build-and-deploy-production.yml (name doesn't matter but needs the .yml extension)



Paste this code in to the file:

/.github/workflows/build-and-deploy-production.yml

name: CI

on:
  push:
    branches: [main]

env:
  REGISTRY: 'registry.mydomainname.com'
  IMAGE_NAME: 'what-you-want-your-image-called'
  SERVICE_NAME: 'website-service-name'

jobs:
  build_and_push:
    runs-on: ubuntu-latest
    steps:
      - name: Checkout the repo
        uses: actions/checkout@v4

      - name: Create next public env file
        run: |
          touch .env
          echo "${{ secrets.NEXT_PUBLIC_ENV_PRODUCTION }}" > .env
          cat .env

      - name: Install Tailscale
        run: |
          curl -fsSL https://tailscale.com/install.sh | sh

      - name: Authenticate to Tailscale
        run: |
          sudo tailscale up --authkey=${{ secrets.TAILSCALE_AUTH_KEY }} --hostname=github-action-runner

      - name: Build container image
        run: docker build --build-arg DATABASE_URI=${{ secrets.DATABASE_URI_PRODUCTION }} --build-arg PAYLOAD_SECRET=${{ secrets.PAYLOAD_SECRET_PRODUCTION }} -t $REGISTRY/$IMAGE_NAME:latest .

      - name: Log in to Private Container Registry
        run: |
          echo ${{ secrets.REGISTRY_PASSWORD_PRODUCTION }} | docker login $REGISTRY -u ${{ secrets.REGISTRY_USER_PRODUCTION }} --password-stdin

      - name: Push image to Container Registry
        run: docker push $REGISTRY/$IMAGE_NAME:latest
# deploy:
# runs-on: ubuntu-latest
# needs: build_and_push
#
# steps:
# - name: Install Tailscale
# run: |
# curl -fsSL https://tailscale.com/install.sh | sh
#
# - name: Authenticate to Tailscale
# run: |
# sudo tailscale up --authkey=${{ secrets.TAILSCALE_AUTH_KEY }} --hostname=github-action-runner
#
# - name: Deploy to Docker Swarm via SSH action
# uses: appleboy/ssh-action@master
# with:
# host: ${{ secrets.HOST_PRODUCTION }}
# username: ${{ secrets.USERNAME_PRODUCTION }}
# password: ${{ secrets.PASSWORD_PRODUCTION }}
# port: 22
# envs: IMAGE_NAME,REGISTRY,SERVICE_NAME
# script: |
# echo "Updating service..."
# echo ${{ secrets.REGISTRY_PASSWORD_PRODUCTION }} | docker login -u ${{ secrets.REGISTRY_USER_PRODUCTION }} --password-stdin $(echo $REGISTRY)
# docker service update --force --with-registry-auth --image $(echo $REGISTRY)/$(echo $IMAGE_NAME):latest $(echo $SERVICE_NAME)

Replace the registry, image and service names at the top with your values.

  • Registry: Your registry domain/sub-domain set up earlier.
  • Image: Anything meaningful, its what it will be called in the registry and docker.
  • Service: A meningful name for that websites service, you will use the same name you put here when setting up the docker service in the next sections.


The file is in 2 parts.

  1. build_and_push
    1. Checks out the main repo code.
    2. Creates an env file from the NEXT_PUBLIC secret values.
    3. Installs Tailscale application.
    4. Creates a Tailscale service using the auth key so the build runner is connected to your Tailscale network.
    5. Builds the docker image, passing the db and secret build arguments in and saves it as the image name with latest tag.
    6. Logs in to the private registry.
    7. Pushes the image to the registry.
  2. deploy
    1. Installs Tailscale application.
    2. Creates a Tailscale service using the auth key so the deploy runner is connected to your Tailscale network.
    3. SSH to your server over Tailscale, Logs in to the private registry, updates the service with the new latest image.

The second deploy section is commented out in the code above. We want to run it first with just part 1, so it builds the image and pushes it to the repository. Then in the next section we manually create the service in Portainer using the image thats then in the registry, then we will uncomment out the code in the workflow and do another push to main branch to kick off the whole build and deploy process again, this time with a service that exists that we will be updating.


So now, with the deploy part still commented out, save your code, merge to main and push to GitHub. This will kick off the workflow automatically, you can watch its progress in the 'Actions' tab of the GitHub website. If there are any failures then you get an email notification from GitHub.


Products from our shop

Docker Cheat Sheet - Print at Home Designs

Docker Cheat Sheet - Print at Home Designs

Docker Cheat Sheet Mouse Mat

Docker Cheat Sheet Mouse Mat

Docker Cheat Sheet Travel Mug

Docker Cheat Sheet Travel Mug

Docker Cheat Sheet Mug

Docker Cheat Sheet Mug

Vim Cheat Sheet - Print at Home Designs

Vim Cheat Sheet - Print at Home Designs

Vim Cheat Sheet Mouse Mat

Vim Cheat Sheet Mouse Mat

Vim Cheat Sheet Travel Mug

Vim Cheat Sheet Travel Mug

Vim Cheat Sheet Mug

Vim Cheat Sheet Mug

SimpleSteps.guide branded Travel Mug

SimpleSteps.guide branded Travel Mug

Developer Excuse Javascript - Travel Mug

Developer Excuse Javascript - Travel Mug

Developer Excuse Javascript Embroidered T-Shirt - Dark

Developer Excuse Javascript Embroidered T-Shirt - Dark

Developer Excuse Javascript Embroidered T-Shirt - Light

Developer Excuse Javascript Embroidered T-Shirt - Light

Developer Excuse Javascript Mug - White

Developer Excuse Javascript Mug - White

Developer Excuse Javascript Mug - Black

Developer Excuse Javascript Mug - Black

SimpleSteps.guide branded stainless steel water bottle

SimpleSteps.guide branded stainless steel water bottle

Developer Excuse Javascript Hoodie - Light

Developer Excuse Javascript Hoodie - Light

Developer Excuse Javascript Hoodie - Dark

Developer Excuse Javascript Hoodie - Dark

© 2025 SimpleSteps.guide
AboutFAQPoliciesContact