This guide shows how to start, stop, reboot, describe, and create EC2 instances using Go with the AWS SDK v2. The examples require Go 1.21+, Go modules enabled, and credentials configured via AWS profile or environment variables.
Prerequisites
- AWS account with EC2 permissions (principle of least privilege).
- Go 1.21 or higher.
- Credentials configured with
aws configure
or variablesAWS_ACCESS_KEY_ID
,AWS_SECRET_ACCESS_KEY
,AWS_REGION
.
Example of minimal IAM policy
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"ec2:DescribeInstances",
"ec2:DescribeInstanceStatus",
"ec2:StartInstances",
"ec2:StopInstances",
"ec2:RebootInstances"
],
"Resource": "*"
}
]
}
To create/terminate instances add: ec2:RunInstances
, ec2:TerminateInstances
, ec2:CreateTags
, plus permissions on key pairs and security groups.
Module installation
go mod init example.com/ec2-go
go get github.com/aws/aws-sdk-go-v2@latest
go get github.com/aws/aws-sdk-go-v2/config@latest
go get github.com/aws/aws-sdk-go-v2/service/ec2@latest
EC2 client configuration
// internal/aws/ec2client/ec2client.go
package ec2client
import (
"context"
"os"
"github.com/aws/aws-sdk-go-v2/config"
"github.com/aws/aws-sdk-go-v2/service/ec2"
)
func New(ctx context.Context) (*ec2.Client, error) {
region := os.Getenv("AWS_REGION")
opts := []func(*config.LoadOptions) error{}
if region != "" {
opts = append(opts, config.WithRegion(region))
}
cfg, err := config.LoadDefaultConfig(ctx, opts...)
if err != nil {
return nil, err
}
return ec2.NewFromConfig(cfg), nil
}
Common operations on an existing instance
Suppose we have the instance ID, e.g. i-0123456789abcdef0
. Let’s create reusable functions:
Describe: state and details
// internal/ec2ops/describe.go
...
return info, nil
}
func value[T ~string](p *T) string {
if p == nil {
return ""
}
return string(*p)
}
Start, stop, reboot
// internal/ec2ops/lifecycle.go
...
return err
}
Wait for state transitions (waiters)
We use native waiters to wait for running or stopped with a timeout.
// internal/ec2ops/waiters.go
...
return waiter.Wait(ctx, &ec2.DescribeInstancesInput{InstanceIds: []string{id}}, 10*time.Second)
}
Create a new instance
To create an instance you need AMI, type, key pair (SSH), security group and subnet. Example with tags and user data to install Nginx on Amazon Linux 2023.
// internal/ec2ops/create.go
...
func str(s string) *string { return &s }
Secure credential and role management
- Prefer IAM Roles on runners/instances instead of static keys.
- For local development, use profiles in
~/.aws/credentials
andAWS_PROFILE
. - Separate read-only policies (describe) from lifecycle policies (start/stop/run/reboot).
Operational best practices
- Idempotency: check state before calling
StartInstances
/StopInstances
. - Backoff: handle throttling with exponential retry (middleware if needed).
- Waiters: use waiters for consistent states before next steps (IP assignment, health check, tagging).
- Tagging: adopt consistent tags for cost allocation (
Project
,Env
,Owner
). - Security: protect the key pair’s private key; restrict Security Groups (SSH only from your IP).
- Shutdown: schedule stop of unused instances (cron/CI).
Quick diagnostics
// internal/ec2ops/status.go
...
func boolp(b bool) *bool { return &b }
Useful environment variables
AWS_ACCESS_KEY_ID=<your_access_key>
AWS_SECRET_ACCESS_KEY=<your_secret_key>
AWS_REGION=eu-central-1
AWS_PROFILE=default
Conclusion
With the AWS SDK v2 for Go and a few well-structured functions you can orchestrate the entire lifecycle of an EC2 instance: from creation with user data to state checking, up to scheduled stop. Adapt these examples to your IAM policies, security groups, and network/observability requirements.