diff --git a/README.md b/README.md
index 89efea4..8c9ec19 100644
--- a/README.md
+++ b/README.md
@@ -207,6 +207,7 @@ The following inputs can be used as `step.with` keys
| `tf_state_file_name_append` | String | Appends a string to the tf-state-file. Setting this to `unique` will generate `tf-state-aws-unique`. (Can co-exist with `tf_state_file_name`) |
| `tf_state_bucket` | String | AWS S3 bucket name to use for Terraform state. See [note](#s3-buckets-naming) |
| `tf_state_bucket_destroy` | Boolean | Force purge and deletion of S3 bucket defined. Only evaluated when `tf_stack_destroy` is also `true`, so it is safe to leave this enabled when standing up your stack. Defaults to `false`. |
+| `tf_state_bucket_prevent_destroy` | Boolean | Prevent Terraform from destroying the S3 state bucket. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |
| `tf_targets` | List | A list of targets to create before the full stack creation. |
| `ansible_skip` | Boolean | Skip Ansible execution after Terraform excecution. Default is `false`.|
| `ansible_ssh_to_private_ip` | Boolean | Make Ansible connect to the private IP of the instance. Only usefull if using a hosted runner in the same network. Default is `false`. |
@@ -243,6 +244,7 @@ The following inputs can be used as `step.with` keys
| `aws_ec2_user_data_file` | String | Relative path in the repo for a user provided script to be executed with Terraform EC2 Instance creation. See [this note](https://docs.aws.amazon.com/AWSEC2/latest/UserGuide/user-data.html#user-data-shell-scripts). Make sure the add the executable flag to the file. |
| `aws_ec2_user_data_replace_on_change`| Boolean | If `aws_ec2_user_data_file` file changes, instance will stop and start. Hence public IP will change. This will destroy and recreate the instance. Defaults to `true`. |
| `aws_ec2_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to ec2 provisioned resources.|
+| `aws_ec2_prevent_destroy` | Boolean | Prevent Terraform from destroying the EC2 instance. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |
@@ -261,6 +263,7 @@ The following inputs can be used as `step.with` keys
| `aws_vpc_single_nat_gateway` | Boolean | Toggles only one NAT gateway for all of the public subnets. Defaults to `false`. |
| `aws_vpc_external_nat_ip_ids` | String | **Existing** comma separated list of IP IDs if reusing. (ElasticIPs). |
| `aws_vpc_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to vpc provisioned resources.|
+| `aws_vpc_prevent_destroy` | Boolean | Prevent Terraform from destroying the VPC. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |
@@ -276,6 +279,7 @@ The following inputs can be used as `step.with` keys
| `aws_r53_create_root_cert` | Boolean | Generates and manage the root cert for the application. **See note**. Default is `false`. |
| `aws_r53_create_sub_cert` | Boolean | Generates and manage the sub-domain certificate for the application. **See note**. Default is `false`. |
| `aws_r53_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to R53 provisioned resources.|
+| `aws_r53_cert_prevent_destroy` | Boolean | Prevent Terraform from destroying Route53 certificates. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |
@@ -292,6 +296,7 @@ The following inputs can be used as `step.with` keys
| `aws_elb_access_log_bucket_name` | String | S3 bucket name to store the ELB access logs. Defaults to `${aws_resource_identifier}-logs` (or `-lg `depending of length). **Bucket will be deleted if stack is destroyed.** |
| `aws_elb_access_log_expire` | String | Delete the access logs after this amount of days. Defaults to `90`. Set to `0` in order to disable this policy. |
| `aws_elb_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to elb provisioned resources.|
+| `aws_elb_prevent_destroy` | Boolean | Prevent Terraform from destroying the load balancer. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |
@@ -334,6 +339,7 @@ The following inputs can be used as `step.with` keys
| `aws_efs_mount_target` | String | Directory path in efs to mount directory to. Default is `/`. |
| `aws_efs_ec2_mount_point` | String | The `aws_efs_ec2_mount_point` input represents the folder path within the EC2 instance to the data directory. Default is `/user/ubuntu//data`. Additionally, this value is loaded into the docker-compose `.env` file as `HOST_DIR`. |
| `aws_efs_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to efs provisioned resources.|
+| `aws_efs_prevent_destroy` | Boolean | Prevent Terraform from destroying the EFS file system. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. |
@@ -365,8 +371,9 @@ The following inputs can be used as `step.with` keys
| `aws_rds_db_cloudwatch_logs_exports`| String | Set of log types to enable for exporting to CloudWatch logs. Defaults to `postgresql`. Options are MySQL and MariaDB: `audit,error,general,slowquery`. PostgreSQL: `postgresql,upgrade`. MSSQL: `agent,error`. Oracle: `alert,audit,listener,trace`. |
| `aws_rds_db_multi_az` | Boolean| Specifies if the RDS instance is multi-AZ. Defaults to `false`. |
| `aws_rds_db_maintenance_window` | String | The window to perform maintenance in. Eg: `Mon:00:00-Mon:03:00` |
-| `aws_rds_db_apply_immediately` | Boolean | Specifies whether any database modifications are applied immediately, or during the next maintenance window. Defaults to `false`.|
+| `aws_rds_db_apply_immediately` | Boolean | Specifies whether any database modifications are applied immediately, or during the next maintenance window. Defaults to `false`.|
| `aws_rds_db_additional_tags` | JSON | Add additional tags to the terraform [default tags](https://www.hashicorp.com/blog/default-tags-in-the-terraform-aws-provider), any tags put here will be added to RDS provisioned resources.|
+| `aws_rds_db_prevent_destroy` | Boolean | Prevent Terraform from destroying the RDS database. Set to `true` to enable lifecycle prevent_destroy. Defaults to `true`. **Important: This protects your database from accidental deletion.** |
@@ -463,6 +470,61 @@ Option 2, you have access to the `aws_efs_fs_id` attributes, which will mount an
If you set `aws_efs_create_mount_target` and `aws_efs_create_ha`, mount targets will be created for all of the availability zones of the region.
+## Protecting Resources with Prevent Destroy
+
+To protect critical infrastructure from accidental deletion, the `prevent_destroy` lifecycle flag is **enabled by default** on various AWS resources. When enabled, Terraform will refuse to destroy the resource, providing an extra layer of safety for your production infrastructure.
+
+### Available Prevent Destroy Flags
+
+The following resources have `prevent_destroy` enabled by default (all default to `true`):
+
+- **`tf_state_bucket_prevent_destroy`** - Protects the S3 bucket containing Terraform state
+- **`aws_ec2_prevent_destroy`** - Protects the EC2 instance
+- **`aws_vpc_prevent_destroy`** - Protects the VPC infrastructure
+- **`aws_r53_cert_prevent_destroy`** - Protects Route53 certificates
+- **`aws_elb_prevent_destroy`** - Protects the Elastic Load Balancer
+- **`aws_efs_prevent_destroy`** - Protects the EFS file system
+- **`aws_rds_db_prevent_destroy`** - Protects the RDS database
+
+### Example Usage - Disabling Protection
+
+Since protection is enabled by default, you typically only need to explicitly set these flags if you want to **disable** protection:
+
+```yaml
+name: Deploy without resource protection
+on:
+ push:
+ branches: [ develop ]
+
+jobs:
+ EC2-Deploy:
+ runs-on: ubuntu-latest
+ steps:
+ - id: deploy
+ uses: bitovi/github-actions-deploy-docker-to-ec2@v1.0.1
+ with:
+ aws_access_key_id: ${{ secrets.AWS_ACCESS_KEY_ID }}
+ aws_secret_access_key: ${{ secrets.AWS_SECRET_ACCESS_KEY }}
+ aws_default_region: us-east-1
+
+ # Disable protection for development environment
+ aws_ec2_prevent_destroy: false
+ aws_vpc_prevent_destroy: false
+
+ # Keep database protected even in dev
+ aws_rds_db_enable: true
+ aws_rds_db_prevent_destroy: true # Explicitly keep enabled
+```
+
+### Important Notes
+
+- **All resources are protected by default** with `prevent_destroy` set to `true`
+- This provides a safety net against accidental infrastructure deletion
+- When `prevent_destroy` is enabled, you must manually disable it before destroying the stack
+- To destroy a protected resource: set the flag to `false`, apply the changes, then run the destroy operation
+- For production environments, it's recommended to keep these protections enabled
+- The Terraform state bucket protection is independent of `tf_state_bucket_destroy`
+
## Adding external RDS Database
If `aws_rds_db_enable` is set to `true`, this action will deploy a Postgres RDS database.
diff --git a/action.yaml b/action.yaml
index 0a47f90..36f180b 100644
--- a/action.yaml
+++ b/action.yaml
@@ -51,6 +51,10 @@ inputs:
tf_state_bucket_destroy:
description: 'Force purge and deletion of S3 bucket defined. Only evaluated when `tf_stack_destroy` is also true, so it is safe to leave this enabled when standing up your stack.'
required: false
+ tf_state_bucket_prevent_destroy:
+ description: 'Prevent Terraform from destroying the S3 state bucket. Set to true to enable lifecycle prevent_destroy.'
+ required: false
+ default: true
tf_targets: # targets
description: 'A list of targets to create before the full stack creation.'
required: false
@@ -129,6 +133,10 @@ inputs:
aws_ec2_additional_tags:
description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`'
required: false
+ aws_ec2_prevent_destroy:
+ description: 'Prevent Terraform from destroying the EC2 instance. Set to true to enable lifecycle prevent_destroy.'
+ required: false
+ default: true
# AWS VPC Inputs
aws_vpc_create:
@@ -167,6 +175,10 @@ inputs:
aws_vpc_additional_tags:
description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`'
required: false
+ aws_vpc_prevent_destroy:
+ description: 'Prevent Terraform from destroying the VPC. Set to true to enable lifecycle prevent_destroy.'
+ required: false
+ default: true
# AWS Route53 Domains abd Certificates
aws_r53_enable:
@@ -196,6 +208,10 @@ inputs:
aws_r53_additional_tags:
description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`'
required: false
+ aws_r53_cert_prevent_destroy:
+ description: 'Prevent Terraform from destroying Route53 certificates. Set to true to enable lifecycle prevent_destroy.'
+ required: false
+ default: true
# AWS ELB
aws_elb_create:
@@ -230,6 +246,10 @@ inputs:
aws_elb_additional_tags:
description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`'
required: false
+ aws_elb_prevent_destroy:
+ description: 'Prevent Terraform from destroying the load balancer. Set to true to enable lifecycle prevent_destroy.'
+ required: false
+ default: true
# Docker
docker_install:
@@ -322,6 +342,10 @@ inputs:
aws_efs_additional_tags:
description: 'A list of strings that will be added to created resources'
required: false
+ aws_efs_prevent_destroy:
+ description: 'Prevent Terraform from destroying the EFS file system. Set to true to enable lifecycle prevent_destroy.'
+ required: false
+ default: true
# AWS RDS
aws_rds_db_enable: # aws_enable_postgres
@@ -405,6 +429,10 @@ inputs:
aws_rds_db_additional_tags:
description: 'A JSON object of additional tags that will be included on created resources. Example: `{"key1": "value1", "key2": "value2"}`'
required: false
+ aws_rds_db_prevent_destroy:
+ description: 'Prevent Terraform from destroying the RDS database. Set to true to enable lifecycle prevent_destroy.'
+ required: false
+ default: true
# RDS Proxy
aws_db_proxy_name:
@@ -629,6 +657,7 @@ runs:
tf_state_file_name_append: ${{ inputs.tf_state_file_name_append }}
tf_state_bucket: ${{ inputs.tf_state_bucket }}
tf_state_bucket_destroy: ${{ inputs.tf_state_bucket_destroy }}
+ tf_state_bucket_prevent_destroy: ${{ inputs.tf_state_bucket_prevent_destroy }}
tf_targets: ${{ inputs.tf_targets || inputs.targets }}
ansible_skip: ${{ inputs.ansible_skip }}
ansible_ssh_to_private_ip: ${{ inputs.ansible_ssh_to_private_ip }}
@@ -657,6 +686,7 @@ runs:
aws_ec2_user_data_file: ${{ inputs.aws_ec2_user_data_file }}
aws_ec2_user_data_replace_on_change: ${{ inputs.aws_ec2_user_data_replace_on_change }}
aws_ec2_additional_tags: ${{ inputs.aws_ec2_additional_tags }}
+ aws_ec2_prevent_destroy: ${{ inputs.aws_ec2_prevent_destroy }}
## AWS VPC
aws_vpc_create: ${{ inputs.aws_vpc_create }}
@@ -671,6 +701,7 @@ runs:
aws_vpc_single_nat_gateway: ${{ inputs.aws_vpc_single_nat_gateway }}
aws_vpc_external_nat_ip_ids: ${{ inputs.aws_vpc_external_nat_ip_ids }}
aws_vpc_additional_tags: ${{ inputs.aws_vpc_additional_tags }}
+ aws_vpc_prevent_destroy: ${{ inputs.aws_vpc_prevent_destroy }}
# AWS Route53 Domains abd Certificates
aws_r53_enable: ${{ inputs.aws_r53_enable }}
@@ -682,6 +713,7 @@ runs:
aws_r53_create_root_cert: ${{ inputs.aws_r53_create_root_cert || inputs.create_root_cert }}
aws_r53_create_sub_cert: ${{ inputs.aws_r53_create_sub_cert || inputs.create_sub_cert }}
aws_r53_additional_tags: ${{ inputs.aws_r53_additional_tags }}
+ aws_r53_cert_prevent_destroy: ${{ inputs.aws_r53_cert_prevent_destroy }}
# AWS ELB
aws_elb_create: ${{ inputs.aws_elb_create }}
@@ -694,6 +726,7 @@ runs:
aws_elb_access_log_bucket_name: ${{ inputs.aws_elb_access_log_bucket_name }}
aws_elb_access_log_expire: ${{ inputs.aws_elb_access_log_expire }}
aws_elb_additional_tags: ${{ inputs.aws_elb_additional_tags }}
+ aws_elb_prevent_destroy: ${{ inputs.aws_elb_prevent_destroy }}
# Docker
docker_install: ${{ inputs.docker_install }}
@@ -725,6 +758,7 @@ runs:
aws_efs_enable_backup_policy: ${{ inputs.aws_efs_enable_backup_policy || inputs.aws_enable_efs_backup_policy }}
aws_efs_transition_to_inactive: ${{ inputs.aws_efs_transition_to_inactive }}
aws_efs_additional_tags: ${{ inputs.aws_efs_additional_tags }}
+ aws_efs_prevent_destroy: ${{ inputs.aws_efs_prevent_destroy }}
# AWS RDS
aws_rds_db_enable: ${{ inputs.aws_rds_db_enable || inputs.aws_enable_postgres }}
@@ -754,6 +788,7 @@ runs:
aws_rds_db_maintenance_window: ${{ inputs.aws_rds_db_maintenance_window }}
aws_rds_db_apply_immediately: ${{ inputs.aws_rds_db_apply_immediately }}
aws_rds_db_additional_tags: ${{ inputs.aws_rds_db_additional_tags }}
+ aws_rds_db_prevent_destroy: ${{ inputs.aws_rds_db_prevent_destroy }}
# DB Proxy
aws_db_proxy_name : ${{ inputs.aws_db_proxy_name }}