Run shell commands on AWS EC2 instances via SSM — no SSH required. Supports OIDC, static credentials, multi-line commands, and tag-based targeting.
Credits: Based on the original work by Donghyun Peter Kim (peterkimzz). Rewritten with AWS SDK v3, Node 24, OIDC, and tag targeting.
- Requirements
- Quick start
- Examples
- Targeting instances
- Viewing command output
- Running on Windows targets
- Inputs
- Outputs
- Troubleshooting
- GitHub OIDC configured in AWS. Follow the GitHub guide or the AWS IAM OIDC provider docs. Static credentials also work if you prefer.
- IAM permissions on the role or user (minimum policy below).
- EC2 instance role with
AmazonSSMManagedInstanceCoreattached so the SSM agent can register.
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": "ssm:SendCommand",
"Resource": [
"arn:aws:ec2:*:*:instance/*",
"arn:aws:ssm:*:*:document/AWS-RunShellScript"
]
},
{
"Effect": "Allow",
"Action": [
"ssm:GetCommandInvocation",
"ssm:ListCommandInvocations"
],
"Resource": "*"
}
]
}Need broader SSM access (Parameter Store, Session Manager, Patch Manager)? See
AmazonSSMFullAccess— it grants significantly more than this action requires.
name: Deploy via SSM
on:
push:
branches: [main]
concurrency:
group: deploy-${{ github.ref }}
cancel-in-progress: false
jobs:
deploy:
runs-on: ubuntu-latest
permissions: # job-level, not workflow-level
id-token: write
contents: read
steps:
# No checkout needed — the command runs on the EC2 instance, not this runner.
- name: Configure AWS credentials (OIDC)
uses: aws-actions/configure-aws-credentials@ec61189d14ec14c8efccab744f656cffd0e33f37 # v6.1.0
with:
role-to-assume: arn:aws:iam::123456789012:role/my-deploy-role
aws-region: us-east-1
- name: Run commands on EC2
id: ssm
uses: zAbuQasem/ssm-run-command@v2
with:
aws-region: us-east-1
instance-ids: ${{ secrets.INSTANCE_ID }}
working-directory: /home/ubuntu/app
wait-for-output: true
comment: Deploy ${{ github.sha }}
command: |
git pull origin main
npm install --production
pm2 restart all
- run: echo "${{ steps.ssm.outputs.output }}"For stricter supply-chain security, pin to a commit SHA instead of
@v2(seeexamples/for SHA-pinned, production-ready workflows).
Ready-to-use workflow files are in the examples/ directory:
| File | Description |
|---|---|
oidc-deploy.yml |
Standard OIDC deploy — no long-lived credentials |
tag-targeting.yml |
Target instances by EC2 tag instead of instance IDs |
multi-instance.yml |
Dispatch to multiple instances in parallel |
windows-powershell.yml |
Run PowerShell on Windows EC2 targets |
static-credentials.yml |
Use IAM access keys instead of OIDC |
You must provide exactly one of instance-ids or targets — not both.
Newline-separated, up to 50 IDs per call.
with:
instance-ids: |
i-0b1f8b18a1d450000
i-0b1f8b18a1d450001targets follows the AWS CLI --targets format: Key=<key>,Values=<v1>[,<v2>...]. One target per line, up to 5 targets per call. When multiple targets are given, an instance must match all of them.
Valid keys:
tag:<tag-name>— filter by EC2 tagInstanceIds— equivalent toinstance-idsbut via thetargetsAPIresource-groups:Name— filter by AWS Resource Group nameresource-groups:ResourceTypeFilters— filter by resource type within a group
Single tag:
with:
aws-region: us-east-1
targets: |
Key=tag:env,Values=${{ inputs.server }}
command: cd /home/ubuntu/ga-pl && git pull origin main
comment: Deploy ${{ github.sha }} from GitHub ActionsMultiple tags (AND):
with:
targets: |
Key=tag:env,Values=prod
Key=tag:role,Values=web,apiSet document-name to AWS-RunPowerShellScript and pass PowerShell in command:
- uses: zAbuQasem/ssm-run-command@10be2ff3863ef31e5459092c0be8538fbc0907f8 # v2.0.0
with:
aws-region: us-east-1
instance-ids: ${{ secrets.WINDOWS_INSTANCE_ID }}
document-name: AWS-RunPowerShellScript
working-directory: C:\inetpub\wwwroot\app
command: |
git pull origin main
iisresetCustom SSM documents also work — pass either the document name or its full ARN (e.g. arn:aws:ssm:us-east-1:123456789012:document/MyDeployDoc). The document must accept commands and workingDirectory parameters.
| Input | Required | Default | Description |
|---|---|---|---|
aws-region |
Yes | — | AWS region (e.g. us-east-1). |
instance-ids |
One of | — | Newline-separated EC2 instance IDs (max 50). Mutually exclusive with targets. |
targets |
One of | — | Newline-separated Key=<k>,Values=<v1>[,<v2>...] lines (max 5). See Targeting instances. Mutually exclusive with instance-ids. |
document-name |
No | AWS-RunShellScript |
SSM document to execute. Use AWS-RunPowerShellScript for Windows, or provide a custom document name or ARN. The document must accept commands and workingDirectory parameters. |
command |
No | echo $(date) >> logs.txt |
Shell command(s). Each non-empty line runs as a separate step. |
working-directory |
No | /home/ubuntu |
Absolute path where commands execute. |
comment |
No | Executed by Github Actions |
Comment on the SSM Run Command entry (max 100 chars). |
wait-for-output |
No | false |
Wait for the command to finish and stream stdout/stderr into the Actions log. Sets the output action output. |
wait-timeout |
No | 60 |
Seconds to wait before giving up (1–3600). Only relevant when wait-for-output is true. |
aws-access-key-id |
No | — | IAM access key. Not needed when using OIDC. |
aws-secret-access-key |
No | — | IAM secret key. Not needed when using OIDC. |
aws-session-token |
No | — | STS session token for temporary credentials. |
| Output | Description |
|---|---|
command-id |
SSM Run Command UUID. Use with aws ssm get-command-invocation --command-id <id> --instance-id <id> to inspect status and output. |
output |
Combined stdout from all targeted instances. Only set when wait-for-output: true. SSM truncates per-invocation content at 24 000 bytes. |
Set wait-for-output: true to have the action poll until the command finishes and stream the results into the Actions log:
- name: Deploy
id: deploy
uses: zAbuQasem/ssm-run-command@10be2ff3863ef31e5459092c0be8538fbc0907f8 # v2.0.0
with:
aws-region: us-east-1
instance-ids: ${{ secrets.INSTANCE_ID }}
wait-for-output: true
wait-timeout: 120
command: |
cd /home/ubuntu/app
git pull origin main
npm install --production
pm2 restart all
- name: Print output
run: echo "${{ steps.deploy.outputs.output }}"Each instance's stdout and stderr appear in a collapsible group labelled with the instance ID and final status. The action fails if any instance exits with a non-success status or if the timeout is reached before all instances complete.
Note: When using
wait-for-output: truewithtargets(tag-based), thessm:ListCommandInvocationspermission is also required to discover which instances were matched. See the IAM policy below.
AccessDeniedException — the IAM principal lacks ssm:SendCommand. Attach the minimum policy.
InvalidInstanceId: null — the EC2 instance has no AmazonSSMManagedInstanceCore role, or the SSM agent isn't running / reachable.
TargetNotConnected — the instance is tagged/matched correctly but offline from the SSM service. Check the agent and the instance's egress to the SSM endpoints.
Either 'instance-ids' or 'targets' must be provided — you passed neither (or both). Pick exactly one.