Module 3: Infrastructure As Code For An Ai Endpoint

6. The exact limit: why `apply` doesn't run against LocalStack Hobby

Description

This module's previous five lessons all ran for real, without exception: fmt, validate, plan. This lesson documents, with the same honesty and an official citation verified at the moment of writing it — not memorized from an earlier version of this guide — the exact point where that pattern stops: terraform apply (or its tflocal apply wrapper) on module.manifest_extractor_guardrail. Lesson 7, immediately after, traces the fine boundary within this same bedrock.tf — because, as you already saw in lesson 4, not everything in this file shares the same limit.

Connection to the module

This lesson picks back up, for apply, exactly the same pattern this guide's Module 1, lesson 7 already established for awslocal bedrock list-foundation-models: an honest attempt, documented with the official source cited at the exact moment the limit appears — never at the lesson's end, never hidden in a footnote.


Step 1 — What a real apply would need, and why Module 1 already confirmed it

terraform apply on a Bedrock resource, unlike validate and plan (this module's lesson 1), does need to talk to something real: the API that's going to actually create the resource. Against LocalStack, that "something real" is the localstack/localstack container, serving on port 4566. This guide's Module 1, lesson 7 already ran this exact attempt, for real, in this very writing environment:

docker run -d -p 4566:4566 --name localstack-bedrock-test localstack/localstack:latest

What to expect (literal — already executed and documented in this guide's Module 1, lesson 7, reproduced here because it's this lesson's direct foundation):

LocalStack version: 2026.7.4
LocalStack build date: 2026-08-14
LocalStack build git hash: 875b30838

Localstack returning with exit code 55. Reason:
===============================================
License activation failed! 🔑❌

Reason: No credentials were found in the environment. Please make sure to either
set the LOCALSTACK_AUTH_TOKEN variable to a valid auth token. If you are using
the CLI, you can also run `localstack auth set-token`.

Due to this error, Localstack has quit. LocalStack pro features can only be
used with a valid license.

The container starts, prints its version, and shuts itself down with exit code 55 — before exposing a single endpoint, for any service, not just Bedrock. This specific writing environment has no LOCALSTACK_AUTH_TOKEN exported. With a real, free, registered Hobby token, this first obstacle gets resolved, and the container starts — exactly as you already saw with IAM in this module's lesson 4.


Step 2 — The second obstacle, the one not even a Hobby token resolves: the official citation, verified again

This is where this lesson stops repeating Step 1 and becomes genuinely different. With the first obstacle resolved — a real Hobby token, the container started —, the next command would be:

tflocal apply -target=module.manifest_extractor_guardrail -auto-approve

This didn't run in this environment — nor could it, because of Step 1 —, but the conclusion of what would happen with it is built on a source verified at this very moment of writing, not assumed from an earlier version of this guide: LocalStack's official documentation page for Bedrock coverage, checked again today.

The exact quote, verified against docs.localstack.cloud/aws/services/bedrock/: the page's coverage badge states, verbatim:

Included in Plans

Ultimate

No Hobby, no Base — the exact same finding this guide's Module 1, lesson 7 already cited, confirmed again here because it's the direct foundation for why apply stops at this specific point, not another.

What to expect (representative — precisely reconstructed against LocalStack's documented behavior for a service outside the active plan, the same pattern this guide's Module 1, lesson 7 already used for list-foundation-models):

╷
│ Error: creating Bedrock Guardrail (andes-cargo-manifest-extractor-guardrail):
│ operation error Bedrock: CreateGuardrail, https response error StatusCode: 501,
│ RequestID: ..., InternalFailure: API for service 'bedrock' is not included in
│ your current license plan. Please contact LocalStack support or upgrade your
│ plan to access this API.
│
│   with module.manifest_extractor_guardrail.aws_bedrock_guardrail.this,
│   on modules/bedrock-guardrail/main.tf line 1, in resource "aws_bedrock_guardrail" "this":
│    1: resource "aws_bedrock_guardrail" "this" {
│
╵

If it happened for real, the error would show up during apply, never during plan — exactly the distinction this module's lesson 1 established: plan never needs to touch the real API for a new resource; apply does, because at some point it has to make the real CreateGuardrail call, and that's where LocalStack's license-plan limit becomes visible.


The two steps, in a single diagram, applied this time to apply

   THE ATTEMPT AGAINST bedrock.tf WITH apply, STEP BY STEP

   tflocal apply -target=module.manifest_extractor_guardrail
              │
              ▼
   docker run localstack/localstack:latest
              │
              ▼
   LOCALSTACK_AUTH_TOKEN exported?
              │
      NO ─────┴───── YES
      │               │
      ▼               ▼
   Exit code 55    Container starts,
   (this sandbox,   exposes :4566
   confirmed in         │
   Module 1, L7)        ▼
                   terraform apply calls
                   CreateGuardrail against
                   the Bedrock endpoint
                        │
                        ▼
                  Ultimate plan active?
                        │
                NO ─────┴───── YES
                │               │
                ▼               ▼
           "not included    The guardrail
           in your current  actually gets
           license plan"    created -- but
           (Hobby, even     WITHOUT real
           with registered  inference invoked,
           token)           see Module 1, L7's
                             final note

The left branch above — exit code 55 — is exactly what this writing sandbox confirmed, for real, in Step 1. The right branch, even followed through to the end with a Hobby token, stops at the exact same point this guide's Module 1, lesson 7 already documented for list-foundation-models: Bedrock specifically requires LocalStack's highest paid plan, one tier above what any free account offers.


Why this module, precisely because of this, remains the guide's most executed one

It's worth stating plainly what this lesson does not change: this module's previous five lessons — the resource landscape, the complete bedrock-guardrail module, the least-privilege role, the seventeen-resource plan — all ran, for real, with no dependency at all on what this lesson just documented. The apply limit is real, cited with its exact source, and it doesn't roll back anything already confirmed — it's, precisely, the reason this guide so carefully separates "this ran" from "this is representative," lesson by lesson, instead of a single generic warning at the module's end.


Common mistakes

Thinking Step 1's exit code 55 is the same problem as Step 2's license error (merging two independent obstacles mistake, the same one already named in Module 1, lesson 7). What happens: someone concludes "LocalStack doesn't work" as a single cause, without distinguishing the specific environment from the service's limit. How to spot it: if you can't explain why exporting a token wouldn't resolve Step 2. How to fix it: these are two completely independent limits, already precisely distinguished in Module 1, lesson 7 — the first is exclusive to this writing sandbox (anyone with a Hobby token resolves it); the second persists even with that token, because Bedrock specifically requires the Ultimate plan.

Concluding that, since apply doesn't run here, no apply in this ecosystem ever ran for real (scope over-generalization mistake, again). What happens: someone, after seeing this limit, starts retroactively distrusting the real applys that did happen in previous guides (S3, DynamoDB, Lambda, IAM). How to spot it: if your reaction is "so nothing before this got actually applied either." How to fix it: this same module's lesson 7, immediately after, precisely confirms that lesson 4's IAM role does belong to the "on your own machine, with your own token, this gets applied for real" category — this lesson's limit is Bedrock-specific, not a blanket condemnation of this ecosystem's $0 lab.

Expecting a LocalStack Ultimate token to completely solve the problem of "seeing a real response from a model" (forward-looking expectation about what Ultimate would cover mistake). What happens: someone reads "Ultimate" and assumes that, with that paid plan, extract-shipment-manifest-fields would produce a real extraction from a manifest. How to spot it: if your plan, after reading this lesson, is "I'll pay for Ultimate and I already have real inference." How to fix it: Module 1, lesson 7 already warned about this, in its final note — even with Ultimate, LocalStack runs a local Ollama model as Bedrock's substitute: it's real inference, but from a different model, never AWS's actual Bedrock model nor identical to production. A successful apply against Ultimate would actually create the guardrail in the emulated account, but no later invocation (bedrock-runtime invoke-model) would produce a response from Bedrock's real model — the exact reason this guide, declared since Module 1, never runs a real Bedrock inference, on any LocalStack plan.


Exercises

Exercise 1 — Explain why this lesson's representative error shows up during apply, and not during plan, even though both commands "touch" the same bedrock.tf. Go back to this module's lesson 1's mechanism to answer precisely.

See solution

Because plan, for a new resource, never needs to make the real call (CreateGuardrail) Bedrock's API would have to process — the complete diff gets built solely from the declared HCL, with no outgoing request. apply, on the other hand, exists specifically to execute that call for real: at some point in its execution, it has to send the CreateGuardrail request to the configured endpoint (LocalStack or real AWS) and wait for a response. It's exactly at that point — the real call, not building the diff — where LocalStack's license-plan limit becomes visible, because that's where LocalStack, if it were running with a Hobby token, would respond with the license error instead of creating the resource.

Exercise 2 — Compare this lesson's diagram with Module 1, lesson 7's. Both have the same general shape (two obstacles, one from the environment, one from the service). What specifically changed between one and the other?

See solution

The shape is the same — the two obstacles are independent, one solvable with a token, the other not —, but the command triggering the second obstacle changed: in Module 1, lesson 7, it was a read operation (awslocal bedrock list-foundation-models, querying the model catalog). In this lesson, it's a write operation (tflocal apply, trying to actually create a guardrail). The underlying result is the same — Bedrock isn't available outside Ultimate, for any operation —, but this lesson confirms the limit isn't exclusive to reads: it applies equally to trying to create new infrastructure.

Exercise 3 — Predict what would happen if you tried tflocal apply on module.bedrock_manifest_extractor_role (the IAM role, not the guardrail) on a machine with LocalStack Hobby running, with a real token. Without jumping ahead to read all of lesson 7, would you expect the same license error this lesson documented for the guardrail?

See solution

No. IAM, unlike Bedrock, is indeed included in LocalStack's Hobby plan — confirmed since aws-core-services-guide —, so a real apply against that role, with a Hobby (not Ultimate) token exported, would finish with no license error: the role would actually get created, with its inline policy, in LocalStack's emulated account. This is exactly the contrast this module's lesson 7 develops in detail — two resources in the same bedrock.tf, with two completely different execution boundaries.


Summary and next step

This lesson documented, precisely and with an official source verified again today against docs.localstack.cloud/aws/services/bedrock/, this module's exact limit: terraform apply on the Bedrock guardrail stops at two independent obstacles — this writing sandbox's real exit code 55 (Module 1, lesson 7), and the official "Included in Plans: Ultimate" badge, which would persist even with a real Hobby token. Nothing executed in this module's previous five lessons changes because of this: fmt, validate, and plan ran, for real, with no dependency, at any point, on this specific apply limit.

Before moving on you should be able to: explain why this lesson's limit shows up in apply and not in plan; cite, from memory or close to it, LocalStack's badge's exact phrase about Bedrock; and anticipate, without lesson 7 confirming it yet, that lesson 4's IAM role belongs to a different category within this same bedrock.tf.

Lesson 7 confirms exactly that distinction: within the same file, one resource stops here (Bedrock, Ultimate-only) and the other doesn't (IAM, Hobby) — the exact boundary, with the same honesty, without generalizing in either direction.

Resources

  1. LocalStack Docs — Bedrock — this lesson's exact source, verified again on the day of writing: "Included in Plans: Ultimate".
  2. LocalStack — Pricing — a complete comparison of plans and services included in each.
  3. This guide's Module 1, lesson 7 (07-hands-on-the-honest-attempt-against-bedrock-on-localstack.md) — the source of the real exit code 55 and the same "honest attempt" pattern this lesson reapplies to apply.
  4. Terraform Docs — Command: apply — official reference, including the distinction between building the plan and executing it against a real endpoint.