DEV Community

Terry Leonard Hunt Jr
Terry Leonard Hunt Jr

Posted on • Originally published at terryhunt.dev

Secure Strapi Deployment on Amazon AWS

☑️ Prerequisites

☁️ Strapi Cloud

You can also use Strapi Cloud to deploy and host your project quickly.


Security First IAM Setup

1. Create IAM User with Minimal Permissions

Security Note: The official Strapi guide suggests broad permissions. The following approach applies hardened, minimal, service specific permissions.

Create Administrator User (One time setup)

Create Strapi Developer User with Hardened Permissions

Instead of using *FullAccess policies, create custom policies with only the required capabilities.

EC2 Minimal Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ec2:DescribeInstances",
        "ec2:StartInstances",
        "ec2:StopInstances",
        "ec2:RebootInstances"
      ],
      "Resource": "*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

RDS Minimal Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "rds:DescribeDBInstances",
        "rds:DescribeDBClusters"
      ],
      "Resource": "*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

S3 Secure Policy (replace with your bucket name):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "StrapiS3Access",
      "Effect": "Allow",
      "Action": [
        "s3:PutObject",
        "s3:GetObject",
        "s3:ListBucket",
        "s3:DeleteObject",
        "s3:PutObjectAcl"
      ],
      "Resource": [
        "arn:aws:s3:::s3-bucket-name",
        "arn:aws:s3:::s3-bucket-name/*"
      ]
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

Secrets Manager Policy:

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "secretsmanager:GetSecretValue"
      ],
      "Resource": "arn:aws:secretsmanager:your-region:your-account-id:secret:your-secret-name*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

SES Policy (if using emails):

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Effect": "Allow",
      "Action": [
        "ses:SendEmail",
        "ses:SendRawEmail"
      ],
      "Resource": "*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

AWS VPC Setup

Follow Strapi's original VPC guide but ensure you:

  1. Select the correct AWS region
  2. Use two Availability Zones for redundancy
  3. Configure two public subnets and two private subnets
  4. Enable DNS hostnames and DNS resolution

EC2 Instance with IAM Role (Recommended)

1. Create IAM Role for EC2

Instead of access keys, assign an IAM role to your instance.

  1. Go to IAM → Roles → Create Role
  2. Select EC2
  3. Attach the minimal policies created earlier
  4. Name the role StrapiEC2Role

2. Launch EC2 Instance

  • Choose Ubuntu Server 22.04 LTS
  • Use t2.small or higher
  • Attach the role StrapiEC2Role
  • Configure security groups with minimal ports:

    • SSH 22 restricted to your IP
    • HTTP 80
    • HTTPS 443
    • Custom TCP 1337 only for testing

Security Warning: Remove port 1337 after configuring NGINX.


Secure RDS Database Setup

1. Create PostgreSQL Database

Security best practices:

  • Use the latest PostgreSQL engine
  • Select Production templates
  • Connect RDS to your VPC
  • Use private subnets
  • Enable encryption at rest
  • Enable automated backups
  • Enable Performance Insights

2. Database Security Best Practices

  • Use strong passwords longer than 20 characters
  • Encrypt data in transit
  • Restrict RDS access to only your EC2 security group
  • Apply routine updates

Secure S3 Configuration

Critical S3 Security Update

Avoid the public bucket policy in the original guide. It exposes all actions publicly.

1. Create Private S3 Bucket

  • Enable “Block all public access”
  • Enable versioning
  • Enable encryption

2. Secure Access Options

Option A: Private with Signed URLs (Recommended)
Uses temporary signed URLs for access.

Option B: Public Read Only (if required)

{
  "Version": "2012-10-17",
  "Statement": [
    {
      "Sid": "PublicReadGetObject",
      "Effect": "Allow",
      "Principal": "*",
      "Action": "s3:GetObject",
      "Resource": "arn:aws:s3:::your-bucket-name/*"
    }
  ]
}
Enter fullscreen mode Exit fullscreen mode

This allows reading but not modifying files.


Secure Environment Variable Management

1. AWS Secrets Manager Setup

Store sensitive variables in Secrets Manager.

Create a secret:

aws secretsmanager create-secret \
  --name "strapi/production/env" \
  --description "Strapi production environment variables" \
  --secret-string file://secret.json \
  --region us-east-1
Enter fullscreen mode Exit fullscreen mode

Example secret.json:

{
  "APP_KEYS": "your-app-keys",
  "API_TOKEN_SALT": "your-api-token-salt",
  "ADMIN_JWT_SECRET": "your-admin-jwt-secret",
  "JWT_SECRET": "your-jwt-secret",
  "DATABASE_HOST": "your-rds-endpoint.rds.amazonaws.com",
  "DATABASE_PORT": "5432",
  "DATABASE_NAME": "strapi",
  "DATABASE_USERNAME": "postgres",
  "DATABASE_PASSWORD": "your-secure-password",
  "AWS_REGION": "us-east-1",
  "AWS_BUCKET_NAME": "your-bucket-name"
}
Enter fullscreen mode Exit fullscreen mode

2. Environment Fetch Scripts

Goal: never commit .env files to Git.

Option A: Bash Script

#!/bin/bash
# fetch-env.sh

SECRET_NAME="strapi/production/env"
REGION="us-east-1"
OUTPUT_FILE=".env"

echo "Fetching secrets..."

if ! command -v aws >/dev/null; then
  echo "AWS CLI missing"
  exit 1
fi

if ! command -v jq >/dev/null; then
  echo "jq missing"
  exit 1
fi

SECRET_JSON=$(aws secretsmanager get-secret-value \
  --secret-id "$SECRET_NAME" \
  --region "$REGION" \
  --query SecretString \
  --output text 2>/dev/null)

echo "$SECRET_JSON" | jq -r 'to_entries|map("\(.key)=\(.value)")|.[]' > "$OUTPUT_FILE"
Enter fullscreen mode Exit fullscreen mode

Make executable:

chmod +x fetch-env.sh
./fetch-env.sh
Enter fullscreen mode Exit fullscreen mode

Option B: Node.js Script

const fs = require('fs');
const { SecretsManagerClient, GetSecretValueCommand } = require('@aws-sdk/client-secrets-manager');

const client = new SecretsManagerClient({
  region: process.env.AWS_REGION || 'us-east-1'
});

async function fetchSecret(secretName, outputPath = '.env') {
  try {
    const command = new GetSecretValueCommand({ SecretId: secretName });
    const response = await client.send(command);
    const secret = JSON.parse(response.SecretString);

    const envData = Object.entries(secret)
      .map(([key, val]) => `${key}=${val}`)
      .join('\n');

    fs.writeFileSync(outputPath, envData);
  } catch (err) {
    console.error('Error fetching secret:', err.message);
    process.exit(1);
  }
}

const secretName = process.argv[2] || 'strapi/production/env';
fetchSecret(secretName);
Enter fullscreen mode Exit fullscreen mode

Install dependencies:

npm install @aws-sdk/client-secrets-manager
node fetch-secrets.js
Enter fullscreen mode Exit fullscreen mode

3. Secure PM2 Configuration

module.exports = {
  apps: [
    {
      name: 'strapi-secure',
      cwd: '/home/ubuntu/your-strapi-project',
      script: 'npm',
      args: 'start',
      env_file: '/home/ubuntu/your-strapi-project/.env'
    }
  ]
};
Enter fullscreen mode Exit fullscreen mode

Node.js and Application Setup

1. Install Dependencies

sudo apt-get update
sudo apt-get install -y ca-certificates curl gnupg
sudo mkdir -p /etc/apt/keyrings
curl -fsSL https://deb.nodesource.com/gpgkey/nodesource-repo.gpg.key | sudo gpg --dearmor -o /etc/apt/keyrings/nodesource.gpg
NODE_MAJOR=20
echo "deb [signed-by=/etc/apt/keyrings/nodesource.gpg] https://deb.nodesource.com/node_$NODE_MAJOR.x nodistro main" | sudo tee /etc/apt/sources.list.d/nodesource.list
sudo apt-get update
sudo apt-get install -y nodejs

npm install -g @aws-sdk/client-secrets-manager

mkdir ~/.npm-global
npm config set prefix '~/.npm-global'
echo 'export PATH=~/.npm-global/bin:$PATH' >> ~/.profile
source ~/.profile
Enter fullscreen mode Exit fullscreen mode

2. Deploy Strapi Securely

git clone https://github.com/your-org/your-strapi-repo.git
cd your-strapi-repo

npm install
node fetch-secrets.js
NODE_ENV=production npm run build

npm install pm2@latest -g
pm2 start ecosystem.config.js
pm2 save
pm2 startup
Enter fullscreen mode Exit fullscreen mode

Production Security Checklist

Before Going Live

  • Remove port 1337 access
  • Configure NGINX reverse proxy with TLS
  • Enable CloudWatch logging
  • Enable automated RDS and S3 backups
  • Enable AWS WAF
  • Enable VPC Flow Logs
  • Configure AWS Config
  • Configure log aggregation and automated patches
  • Enable CloudTrail auditing

Ongoing Security

  • Update EC2 packages regularly
  • Rotate database passwords every quarter
  • Monitor CloudTrail logs
  • Review IAM permissions often
  • Update Strapi and dependencies
  • Test backups and recovery procedures

Complete Git Workflow

Commit to Git

fetch-env.sh
fetch-secrets.js
.env.example
.gitignore
deploy.sh
package.json
/src/
/config/
ecosystem.config.js
Enter fullscreen mode Exit fullscreen mode

Never Commit to Git

.env
.env.local
.aws/
secret.json
Enter fullscreen mode Exit fullscreen mode

Recommended .gitignore

.env
.env.*
!.env.example
secret.json
.aws/
Enter fullscreen mode Exit fullscreen mode

Deployment Automation

1. deploy.sh

#!/bin/bash
set -e

SECRET_NAME="strapi/production/env"
REGION="us-east-1"
APP_DIR="/home/ubuntu/your-strapi-project"
PM2_CONFIG="/home/ubuntu/ecosystem.config.js"

cd "$APP_DIR"

git pull origin main
npm ci --production

if [ -f "fetch-env.sh" ]; then
  ./fetch-env.sh
else
  node fetch-secrets.js "$SECRET_NAME"
fi

NODE_ENV=production npm run build

pm2 restart "$PM2_CONFIG" || pm2 start "$PM2_CONFIG"
pm2 save
Enter fullscreen mode Exit fullscreen mode

2. EC2 Deployment

git clone https://github.com/your-org/your-strapi-repo.git
cd your-strapi-repo

chmod +x fetch-env.sh deploy.sh
npm install
./deploy.sh
Enter fullscreen mode Exit fullscreen mode

Subsequent Deployments

cd /home/ubuntu/your-strapi-project
./deploy.sh
Enter fullscreen mode Exit fullscreen mode

3. Webhook Integration

const secret = 'your_webhook_secret';
const repo = '/home/ubuntu/your-strapi-project/';
const http = require('http');
const crypto = require('crypto');
const exec = require('child_process').exec;

http.createServer(function(req, res) {
  req.on('data', function(chunk) {
    let sig = 'sha1=' + crypto
      .createHmac('sha1', secret)
      .update(chunk.toString())
      .digest('hex');

    if (req.headers['x-hub-signature'] == sig) {
      exec(`cd ${repo} && ./deploy.sh`);
    }
  });
  res.end();
}).listen(8080);
Enter fullscreen mode Exit fullscreen mode

Team Development Workflow

Setup for New Members

Initial Setup

git clone https://github.com/your-org/your-strapi-repo.git
cd your-strapi-repo
npm install
aws configure
Enter fullscreen mode Exit fullscreen mode

Generate Environment File

./fetch-env.sh
node fetch-secrets.js strapi/development/env
node fetch-secrets.js strapi/staging/env
Enter fullscreen mode Exit fullscreen mode

Start Development

npm run develop
Enter fullscreen mode Exit fullscreen mode

Developer README Section

## Environment Setup

This project uses AWS Secrets Manager.

### Quick Start
1. Clone the repository
2. Run npm install
3. Run ./fetch-env.sh
4. Run npm run develop
Enter fullscreen mode Exit fullscreen mode

Cost Optimization

AWS Secrets Manager Pricing

  • 0.40 dollars per secret per month
  • 0.05 dollars per 10,000 API calls
  • First 30 days free

Example cost:

  • Storage 0.40 dollars
  • API calls 0.005 dollars
  • Total about 0.41 dollars per month

Cost Saving Tips

  • Use EC2 IAM roles
  • Tag resources properly
  • Use RDS reserved instances
  • Enable S3 lifecycle policies
  • Monitor costs using Cost Explorer

Troubleshooting

1. IAM Permission Errors

  • Ensure IAM role is attached to EC2
  • Ensure policies contain required actions
  • Check resource ARNs

2. Secrets Manager Issues

  • Check secret name and region
  • Verify IAM permissions
  • Check AWS SDK credentials

3. S3 Upload Issues

  • Check bucket policy
  • Check CORS
  • Check IAM permissions

If you want, I can also format this for GitHub README, generate a table of contents, or optimize the Markdown for SEO on dev.to.

Top comments (0)