Skip to content

fix: make Amplify frontend SSR deploy functional - #256

Merged
nourshoreibah merged 2 commits into
mainfrom
fix/amplify-ssr-deploy
Jun 30, 2026
Merged

fix: make Amplify frontend SSR deploy functional#256
nourshoreibah merged 2 commits into
mainfrom
fix/amplify-ssr-deploy

Conversation

@nourshoreibah

@nourshoreibah nourshoreibah commented Jun 30, 2026

Copy link
Copy Markdown
Collaborator

Why

The frontend deployment is broken for 2 reasons

  1. No SSR service role which Amplify requires with our config
  2. Public API base URL not set
  • Add branch-amplify-ssr-role (trust amplify.amazonaws.com) + AWS-managed AdministratorAccess-Amplify; set iam_service_role_arn on the app.
  • Default NEXT_PUBLIC_API_BASE_URL to the deployed API Gateway stage invoke_url (var.api_base_url still overrides when set).

Verification

See terraform plan

🤖 Generated with Claude Code

nourshoreibah and others added 2 commits June 29, 2026 21:40
Amplify app was configured (platform = WEB_COMPUTE, Next.js SSR) but had
two gaps that prevent a working deploy:

- No iam_service_role_arn. SSR/compute apps need a service role for
  Amplify to provision the compute backend and emit CloudWatch logs;
  without it SSR deploys fail. Add branch-amplify-ssr-role
  (trust amplify.amazonaws.com) + AdministratorAccess-Amplify.
- NEXT_PUBLIC_API_BASE_URL defaulted to "", so the built frontend's
  apiFetch fell back to localhost:300x ports and could not reach the
  backend in production. Default it to the deployed API Gateway stage
  invoke_url; var.api_base_url still overrides when set.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
  - Auto-formatted .tf files with terraform fmt
  - Updated README.md with terraform-docs

  Co-authored-by: nourshoreibah <nourshoreibah@users.noreply.github.com>
@github-actions

Copy link
Copy Markdown
Contributor

Terraform Plan 📖 infrastructure/aws

Terraform Initialization ⚙️success

Terraform Validation 🤖success

Terraform Plan 📖success

Show Plan
data.archive_file.lambda_placeholder: Reading...
data.archive_file.lambda_placeholder: Read complete after 0s [id=96878a51e358033297a32b882fd5223cc95fb8a7]
aws_api_gateway_rest_api.branch_api: Refreshing state... [id=2apxzxb0r8]
aws_s3_bucket.reports_bucket: Refreshing state... [id=c4c-branch-generated-reports20251030194253425700000001]
data.aws_caller_identity.current: Reading...
aws_iam_role.lambda_role: Refreshing state... [id=branch-lambda-role]
aws_cognito_user_pool.branch_user_pool: Refreshing state... [id=us-east-2_CxTueqe6g]
data.aws_caller_identity.current: Read complete after 0s [id=489881683177]
aws_s3_bucket.lambda_deployments: Refreshing state... [id=branch-lambda-deployments-489881683177]
data.infisical_secrets.rds_folder: Reading...
data.infisical_secrets.github_folder: Reading...
aws_api_gateway_resource.lambda_resources["users"]: Refreshing state... [id=0dkbds]
aws_api_gateway_resource.lambda_resources["projects"]: Refreshing state... [id=chhy2i]
aws_api_gateway_resource.lambda_resources["auth"]: Refreshing state... [id=u8unad]
aws_api_gateway_resource.lambda_resources["reports"]: Refreshing state... [id=wsnfk2]
aws_api_gateway_resource.lambda_resources["donors"]: Refreshing state... [id=hybur2]
aws_api_gateway_resource.lambda_resources["expenditures"]: Refreshing state... [id=6sdj3w]
data.infisical_secrets.rds_folder: Read complete after 0s
aws_db_instance.branch_rds: Refreshing state... [id=db-AMMYFTORW6XJGRELV7WQZCNHQI]
aws_api_gateway_method.lambda_methods["auth-POST"]: Refreshing state... [id=agm-2apxzxb0r8-u8unad-POST]
aws_api_gateway_method.lambda_methods["auth-GET"]: Refreshing state... [id=agm-2apxzxb0r8-u8unad-GET]
aws_api_gateway_method.lambda_methods["donors-GET"]: Refreshing state... [id=agm-2apxzxb0r8-hybur2-GET]
aws_api_gateway_method.lambda_methods["reports-GET"]: Refreshing state... [id=agm-2apxzxb0r8-wsnfk2-GET]
data.infisical_secrets.github_folder: Read complete after 0s
aws_api_gateway_method.lambda_methods["projects-GET"]: Refreshing state... [id=agm-2apxzxb0r8-chhy2i-GET]
aws_api_gateway_method.lambda_methods["expenditures-GET"]: Refreshing state... [id=agm-2apxzxb0r8-6sdj3w-GET]
aws_api_gateway_method.lambda_methods["expenditures-POST"]: Refreshing state... [id=agm-2apxzxb0r8-6sdj3w-POST]
aws_api_gateway_method.lambda_methods["users-PATCH"]: Refreshing state... [id=agm-2apxzxb0r8-0dkbds-PATCH]
aws_api_gateway_method.lambda_methods["users-GET"]: Refreshing state... [id=agm-2apxzxb0r8-0dkbds-GET]
aws_api_gateway_method.lambda_methods["projects-POST"]: Refreshing state... [id=agm-2apxzxb0r8-chhy2i-POST]
aws_api_gateway_method.lambda_methods["users-POST"]: Refreshing state... [id=agm-2apxzxb0r8-0dkbds-POST]
aws_api_gateway_method.lambda_methods["users-DELETE"]: Refreshing state... [id=agm-2apxzxb0r8-0dkbds-DELETE]
aws_cognito_user_pool_client.branch_client: Refreshing state... [id=570i6ocj0882qu0ditm4vrr60f]
aws_iam_role_policy_attachment.lambda_basic: Refreshing state... [id=branch-lambda-role/arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole]
aws_s3_bucket_versioning.lambda_deployments: Refreshing state... [id=branch-lambda-deployments-489881683177]
aws_s3_object.lambda_placeholder["auth"]: Refreshing state... [id=branch-lambda-deployments-489881683177/auth/initial.zip]
aws_s3_bucket_server_side_encryption_configuration.lambda_deployments: Refreshing state... [id=branch-lambda-deployments-489881683177]
aws_s3_object.lambda_placeholder["expenditures"]: Refreshing state... [id=branch-lambda-deployments-489881683177/expenditures/initial.zip]
aws_s3_object.lambda_placeholder["users"]: Refreshing state... [id=branch-lambda-deployments-489881683177/users/initial.zip]
aws_s3_object.lambda_placeholder["reports"]: Refreshing state... [id=branch-lambda-deployments-489881683177/reports/initial.zip]
aws_s3_object.lambda_placeholder["projects"]: Refreshing state... [id=branch-lambda-deployments-489881683177/projects/initial.zip]
aws_s3_object.lambda_placeholder["donors"]: Refreshing state... [id=branch-lambda-deployments-489881683177/donors/initial.zip]
aws_s3_bucket_public_access_block.reports_bucket_public_access: Refreshing state... [id=c4c-branch-generated-reports20251030194253425700000001]
aws_s3_bucket_policy.reports_bucket_policy: Refreshing state... [id=c4c-branch-generated-reports20251030194253425700000001]
aws_lambda_function.functions["reports"]: Refreshing state... [id=branch-reports]
aws_lambda_function.functions["projects"]: Refreshing state... [id=branch-projects]
aws_lambda_function.functions["expenditures"]: Refreshing state... [id=branch-expenditures]
aws_lambda_function.functions["donors"]: Refreshing state... [id=branch-donors]
aws_lambda_function.functions["users"]: Refreshing state... [id=branch-users]
aws_lambda_function.functions["auth"]: Refreshing state... [id=branch-auth]
aws_api_gateway_integration.lambda_integrations["users-GET"]: Refreshing state... [id=agi-2apxzxb0r8-0dkbds-GET]
aws_api_gateway_integration.lambda_integrations["expenditures-GET"]: Refreshing state... [id=agi-2apxzxb0r8-6sdj3w-GET]
aws_api_gateway_integration.lambda_integrations["auth-POST"]: Refreshing state... [id=agi-2apxzxb0r8-u8unad-POST]
aws_api_gateway_integration.lambda_integrations["users-PATCH"]: Refreshing state... [id=agi-2apxzxb0r8-0dkbds-PATCH]
aws_api_gateway_integration.lambda_integrations["projects-GET"]: Refreshing state... [id=agi-2apxzxb0r8-chhy2i-GET]
aws_api_gateway_integration.lambda_integrations["projects-POST"]: Refreshing state... [id=agi-2apxzxb0r8-chhy2i-POST]
aws_api_gateway_integration.lambda_integrations["reports-GET"]: Refreshing state... [id=agi-2apxzxb0r8-wsnfk2-GET]
aws_api_gateway_integration.lambda_integrations["donors-GET"]: Refreshing state... [id=agi-2apxzxb0r8-hybur2-GET]
aws_api_gateway_integration.lambda_integrations["users-DELETE"]: Refreshing state... [id=agi-2apxzxb0r8-0dkbds-DELETE]
aws_api_gateway_integration.lambda_integrations["auth-GET"]: Refreshing state... [id=agi-2apxzxb0r8-u8unad-GET]
aws_api_gateway_integration.lambda_integrations["expenditures-POST"]: Refreshing state... [id=agi-2apxzxb0r8-6sdj3w-POST]
aws_lambda_permission.api_gateway_permissions["reports"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_api_gateway_integration.lambda_integrations["users-POST"]: Refreshing state... [id=agi-2apxzxb0r8-0dkbds-POST]
aws_lambda_permission.api_gateway_permissions["auth"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_lambda_permission.api_gateway_permissions["projects"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_lambda_permission.api_gateway_permissions["expenditures"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_lambda_permission.api_gateway_permissions["donors"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_lambda_permission.api_gateway_permissions["users"]: Refreshing state... [id=AllowAPIGatewayInvoke]
aws_api_gateway_deployment.branch_deployment: Refreshing state... [id=klh9jv]
aws_api_gateway_stage.branch_stage: Refreshing state... [id=ags-2apxzxb0r8-prod]
aws_amplify_app.frontend: Refreshing state... [id=dbcy90q4o31f7]
aws_amplify_branch.main: Refreshing state... [id=dbcy90q4o31f7/main]

Terraform used the selected providers to generate the following execution
plan. Resource actions are indicated with the following symbols:
  + create
  ~ update in-place

Terraform will perform the following actions:

  # aws_amplify_app.frontend will be updated in-place
  ~ resource "aws_amplify_app" "frontend" {
      ~ environment_variables         = {
          + "NEXT_PUBLIC_API_BASE_URL"  = "https://2apxzxb0r8.execute-api.us-east-2.amazonaws.com/prod"
            # (1 unchanged element hidden)
        }
      + iam_service_role_arn          = (known after apply)
        id                            = "dbcy90q4o31f7"
        name                          = "branch-frontend"
        tags                          = {}
        # (18 unchanged attributes hidden)

        # (2 unchanged blocks hidden)
    }

  # aws_db_instance.branch_rds will be updated in-place
  ~ resource "aws_db_instance" "branch_rds" {
      ~ engine_version                        = "17.9" -> "17.6"
        id                                    = "db-AMMYFTORW6XJGRELV7WQZCNHQI"
        tags                                  = {}
        # (72 unchanged attributes hidden)
    }

  # aws_iam_role.amplify_ssr will be created
  + resource "aws_iam_role" "amplify_ssr" {
      + arn                   = (known after apply)
      + assume_role_policy    = jsonencode(
            {
              + Statement = [
                  + {
                      + Action    = "sts:AssumeRole"
                      + Effect    = "Allow"
                      + Principal = {
                          + Service = "amplify.amazonaws.com"
                        }
                    },
                ]
              + Version   = "2012-10-17"
            }
        )
      + create_date           = (known after apply)
      + force_detach_policies = false
      + id                    = (known after apply)
      + managed_policy_arns   = (known after apply)
      + max_session_duration  = 3600
      + name                  = "branch-amplify-ssr-role"
      + name_prefix           = (known after apply)
      + path                  = "/"
      + tags_all              = (known after apply)
      + unique_id             = (known after apply)

      + inline_policy (known after apply)
    }

  # aws_iam_role_policy_attachment.amplify_ssr will be created
  + resource "aws_iam_role_policy_attachment" "amplify_ssr" {
      + id         = (known after apply)
      + policy_arn = "arn:aws:iam::aws:policy/AdministratorAccess-Amplify"
      + role       = "branch-amplify-ssr-role"
    }

Plan: 2 to add, 2 to change, 0 to destroy.

─────────────────────────────────────────────────────────────────────────────

Saved the plan to: tfplan

To perform exactly these actions, run the following command to apply:
    terraform apply "tfplan"

Pushed by: @nourshoreibah, Action: pull_request

github-actions Bot added a commit that referenced this pull request Jun 30, 2026
@nourshoreibah
nourshoreibah added this pull request to the merge queue Jun 30, 2026
Merged via the queue into main with commit 5c144b4 Jun 30, 2026
16 checks passed
@nourshoreibah
nourshoreibah deleted the fix/amplify-ssr-deploy branch June 30, 2026 22:47
github-actions Bot added a commit that referenced this pull request Jun 30, 2026
nourshoreibah added a commit that referenced this pull request Jun 30, 2026
The aws module's `terraform-apply` has been failing on every merge:

    InvalidParameterCombination: Cannot upgrade postgres from 17.9 to 17.6

main.tf pinned engine_version = "17.6", but RDS auto-applied a minor upgrade to
17.9 on the live instance. Terraform then tried to "downgrade" it, AWS rejected
it, and the whole aws-module apply aborted — which is why #256's Amplify changes
(SSR service role, NEXT_PUBLIC_API_BASE_URL) never actually applied.

Set engine_version to the live 17.9 and disable auto_minor_version_upgrade so
the version can't drift above the pin again; version bumps are now intentional.

Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants