# Pushing Statamic content over SSH

> Use an SSH deploy key so Statamic can push content commits back to your repository with credentials that do not expire.

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=true` and `STATAMIC_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.

```bash
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:

```bash
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:

```bash
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:

1. Go to the Secrets section.
2. Delete the **STATAMIC_GIT_REMOTE** secret.
3. 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 `/tmp` at 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 `origin` back 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_hosts` created by `ssh-keyscan github.com > /tmp/known_hosts` as an extra init command and use `StrictHostKeyChecking=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_repository` scope, then use `https://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.
