A GitHub Action to execute remote shell commands on EC2 instances via SSM.
- Stores command output in a S3 bucket to avoid the ~24 KB log limit
- Works with public or private EC2 instances.
- No need to open or whitelist port 22. 🚀
- Supports Linux instances only for now
- No realtime streaming of command output
- Create a new, private S3 bucket (example name:
project-name-ssm-deployment-logs).- Tip: Add a lifecycle policy to prune old logs.
- Target EC2 instances must have the SSM Agent installed.
- Target EC2 instances must have an IAM role with these permissions:
- Managed policy:
AmazonSSMManagedInstanceCore(AmazonEC2RoleforSSMis deprecated). - Read/write permissions on the S3 log bucket (custom policy), for example:
- Managed policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"s3:PutObject",
"s3:GetObject",
"s3:GetBucketLocation"
],
"Resource": [
"arn:aws:s3:::project-name-ssm-deployment-logs",
"arn:aws:s3:::project-name-ssm-deployment-logs/*"
]
}
]
}on:
push:
branches:
- main
jobs:
Deployment:
runs-on: ubuntu-latest
steps:
- name: Configure AWS Credentials
uses: aws-actions/configure-aws-credentials@v6
with:
aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }}
aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
aws-region: ${{ vars.AWS_REGION }}
- name: Run commands on EC2
uses: ankurk91/aws-ssm-run-command-action@v1
with:
ec2_instance_id: ${{ vars.EC2_INSTANCE_ID }}
run_as_user: ubuntu
log_bucket_name: ${{ vars.LOG_BUCKET_NAME }}
commands: |
set -e
pwd
cd /home/ubuntu
ls -al
echo "Hello from EC2"
| Name | Required | Default | Description |
|---|---|---|---|
ec2_instance_id |
Yes | null |
EC2 Instance ID |
run_as_user |
Yes | null |
A valid Linux user name on remote EC2 |
log_bucket_name |
Yes | null |
S3 Bucket name to store command output logs |
commands |
Yes | null |
Multiline commands to run on server |
comment |
No | GitHub actions |
User-specified information about the command |
s3_prefix |
No | deployments |
S3 bucket prefix |
execution_timeout |
No | 3600 (1 hour) |
Script is forcibly terminated after this number of seconds |
poll_interval_ms |
No | 2000 (2 seconds) |
Milliseconds to poll command results |
| Name | Description |
|---|---|
command-exit-code |
Remote command exit code, or 255 when the script never ran (see below) |
command-status |
SSM invocation status, e.g. Success, Failed, TimedOut, Cancelled |
The step fails whenever command-status is not Success. Prefer it over the exit code when
branching on the outcome: SSM reports no exit code at all if the script never ran — an unreachable
instance, a missing SSM agent, a timeout or a cancellation — and command-exit-code falls back to
255 in those cases, which is indistinguishable from a script that genuinely exited 255.
This action relies on the default behavior of the AWS SDK for Javascript to determine AWS credentials and region. Use the aws-actions/configure-aws-credentials action to configure the GitHub Actions environment with environment variables containing AWS credentials and your desired region.
This action requires the following set of permissions inside pipeline:
{
"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:CancelCommand"
],
"Resource": "*"
},
{
"Effect": "Allow",
"Action": [
"s3:GetObject"
],
"Resource": "arn:aws:s3:::project-name-ssm-deployment-logs/*"
}
]
}Notes:
s3:GetObjecton the log bucket is mandatory. The action reads the command output from S3 on the runner, so without it every run fails with an access denied error instead of printing logs.ssm:CancelCommandis used by the post step to stop the remote command whenever the job stops while the command is still in flight — a cancelled workflow, a jobtimeout-minutes, a runner shutdown or a failed step. Without it, the cancellation is only logged as a warning and the remote script keeps running on the instance until it finishes or hitsexecution_timeout.- The
arn:aws:ec2:*:*:instance/*resource above is the permissive default. It lets any workflow holding these credentials run arbitrary commands as root on every instance in the account. Narrowing it is strongly recommended, either to the exact instance ARNs you deploy to, or via anssm:resourceTag/*condition if the instance IDs change over time.
MIT License.