Managing an Amazon EC2 instance with Go
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 configureor 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/credentialsandAWS_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.