Pushing Statamic content over SSH
Pushing Statamic content over SSH
By default, Statamic applications push content commits back to your repository over HTTPS using a token that the platform manages for you. That works, but the token is tied to your connected Git provider and can expire (GitLab and Bitbucket tokens are short lived), which eventually stops the automatic push. An SSH deploy key does not expire, so it is the most durable way to let Statamic push content on its own. This is also the authentication method Statamic assumes in its own documentation.
This guide sets up a per-repository SSH deploy key for your application. No changes to your repository code are needed, and the container image already includes an SSH client, so there is nothing to install.
When to use this
- Your provider is GitHub or GitLab and you want pushes to keep working indefinitely.
- You have seen content commits stop reaching your repository after the app has been running for a while.
- You prefer a credential scoped to a single repository over a broader account token.
Provider support:
- GitHub - supported. Deploy keys can be given write access.
- GitLab - supported. Deploy keys can be given write access.
- Bitbucket - not supported for pushing. Bitbucket access keys are read only. Use the HTTPS app password method at the end of this guide instead.
Prerequisites
- Admin access to the repository so you can add a deploy key.
- Git integration left enabled on the application (
STATAMIC_GIT_ENABLED=trueandSTATAMIC_GIT_PUSH=true, which are the defaults).
Step 1: Generate a key pair
On your own machine, generate a dedicated key pair for this application. Do not reuse a personal key.
ssh-keygen -t ed25519 -f statamic_deploy_key -N "" -C "statamic-yourapp"
This creates statamic_deploy_key (private) and statamic_deploy_key.pub (public).
Step 2: Add the public key as a write enabled deploy key
GitHub: open your repository, go to Settings, then Deploy keys, then Add deploy key. Paste the contents of statamic_deploy_key.pub, tick Allow write access, and save.
GitLab: open your repository, go to Settings, then Repository, then Deploy keys. Paste the public key, enable Write access allowed, and save.
Step 3: Add the private key as a secret
The platform passes secrets into the container, so store the private key there rather than in your repository. To keep it on a single line, base64 encode it first:
base64 < statamic_deploy_key | tr -d '\n'
Copy the output, then in your application go to the Secrets section and add:
- PLOI_DEPLOY_KEY_B64 - paste the base64 string
Step 4: Add init commands to install the key
Init commands run inside the container every time it starts, before Statamic boots. Go to your application settings and add these four init commands, replacing OWNER/REPO with your repository:
printf '%s' "$PLOI_DEPLOY_KEY_B64" | base64 -d > /tmp/ploi_deploy_key
chmod 600 /tmp/ploi_deploy_key; chown 33:33 /tmp/ploi_deploy_key 2>/dev/null || true
git -C /var/www/html config core.sshCommand "ssh -i /tmp/ploi_deploy_key -o IdentitiesOnly=yes -o StrictHostKeyChecking=accept-new -o UserKnownHostsFile=/tmp/known_hosts"
git -C /var/www/html remote set-url origin git@github.com:OWNER/REPO.git
For GitLab, use git@gitlab.com:OWNER/REPO.git in the last command.
What these do: write the private key to a file the application user (id 33) can read, tell git to use that key for SSH, accept the host key on first connect, and point the origin remote at the SSH URL.
Step 5: Stop the HTTPS remote from overriding SSH
Your application has a secret named STATAMIC_GIT_REMOTE holding the HTTPS repository URL. On each start the platform points origin at that value, which would undo the SSH remote from step 4. Remove it:
- Go to the Secrets section.
- Delete the STATAMIC_GIT_REMOTE secret.
- Leave STATAMIC_GIT_PUSH set to
true.
With that secret gone, the platform leaves your SSH origin in place.
Step 6: Redeploy and verify
Redeploy the application so the init commands take effect. Then make a small content change in the control panel and save it. Within a few seconds Statamic commits the change and pushes it. Confirm the new commit appears on your repository's default branch.
Security considerations
- Use a key dedicated to this one repository, not a personal or reused key. Revoke it from the repository's deploy keys if you retire the app.
- Secrets are also provided to the image build, so treat the private key as present in the built image and limit who can pull your images. A per-repository deploy key limits the blast radius if it leaks.
- The key is written to
/tmpat runtime and is readable only by the application user. - Rotate the key by generating a new pair, updating the deploy key and the secret, and redeploying.
Troubleshooting
- Pushes still do not appear: check that STATAMIC_GIT_REMOTE is actually removed (step 5). If it is still set, the platform keeps resetting
originback to HTTPS on every start. - Permission denied (publickey): the deploy key was added without write access, or the wrong public key was pasted. Re-add it with write access.
- Host key or known hosts errors: the init command uses
accept-new, which trusts the host on first connect. If your policy requires pinning, replace it with a pre seeded/tmp/known_hostscreated byssh-keyscan github.com > /tmp/known_hostsas an extra init command and useStrictHostKeyChecking=yes. - Bad permissions on the key: confirm the second init command ran. SSH refuses a private key that is group or world readable.
Alternative: a long lived HTTPS credential
If you cannot use SSH (for example on Bitbucket), you can supply your own long lived HTTPS credential instead of relying on the platform's managed token. Put the full authenticated URL in the STATAMIC_GIT_REMOTE secret so the platform points origin at it on each start.
- Bitbucket: create an app password with repository write permission, then use
https://USERNAME:APP_PASSWORD@bitbucket.org/OWNER/REPO.git. - GitLab: create a project access token with the
write_repositoryscope, then usehttps://oauth2:TOKEN@gitlab.com/OWNER/REPO.git. - GitHub: not needed. The platform's GitHub token does not expire, so pushes keep working without any change. If you still prefer your own credential, a fine grained personal access token with contents read and write works as
https://TOKEN@github.com/OWNER/REPO.git.
App passwords and access tokens like these do not expire the way the platform's OAuth token can, so pushes keep working. The same security note applies: the credential is present in the built image, so scope it to a single repository.