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
Guide Sections
Guide Comments
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
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.
- build_and_push
- Checks out the main repo code.
- Creates an env file from the NEXT_PUBLIC secret values.
- Installs Tailscale application.
- Creates a Tailscale service using the auth key so the build runner is connected to your Tailscale network.
- Builds the docker image, passing the db and secret build arguments in and saves it as the image name with latest tag.
- Logs in to the private registry.
- Pushes the image to the registry.
- deploy
- Installs Tailscale application.
- Creates a Tailscale service using the auth key so the deploy runner is connected to your Tailscale network.
- 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.