Test Guide - Don't PUBLISH OR DELETE
If you're working on your own project, you can get away with using a local state file for a long time without any issues. However, chaos begins the moment a teammate or a CI/CD pipeline runs Terraform apply in the same window. Neither process knows that the other is running, so both write to the same state file, corrupting it. A failed Terraform operation can disconnect your state file from your actual infrastructure. If your local machine crashes, you could lose all the info Terraform backend has on that setup.
Terraform backend solves this problem by centralizing state management across your team. It stores state files in a central location accessible to your whole team. Not every backend supports state locking. The local backend has no locking at all, while most remote backends support locking through mechanisms that vary by backend type and configuration. Your backend configuration determines the safety and stability of your entire Terraform project.
In this guide, I'll explain how Terraform backends work and why they matter to teams. You'll learn how to configure remote backends on AWS, the most common remote backend for teams running their infrastructure on AWS. I'll also cover backend migration and common infrastructure state issues. HCP (HashiCorp Cloud Platform) doesn't use a standard backend block. It manages state through a cloud block instead, so the guide covers it as a separate topic.
TL;DR: What you will learn
This guide covers everything you need to build a secure, production-grade backend setup:
What a Terraform backend is and how it works
The difference between a local backend and a remote backend
How to configure the S3 backend for your cloud provider
How to migrate state files between backends without data loss
Common backend errors and how to fix them
What is a Terraform backend?
A Terraform backend is the system Terraform uses to store and manage your infrastructure state data. It controls where your state file lives, how Terraform reads and writes to it during operations, and how your team shares access to that state. Most backends support state locking to stop two processes from writing conflicting state data at the same time, though this depends on the backend type you choose.
When you write and run Terraform code, it does not just send instructions to your cloud provider. It first reads the state file to get a record of every infrastructure resource it has built so far. Then, it compares the record to your Terraform configuration and applies the difference. After applying those changes, Terraform writes the updated state back to your backend. This keeps future operations in sync with your current infrastructure. Without a backend, Terraform cannot track resources or handle changes from multiple sources.
What is the Terraform state file, and why does it matter?
The Terraform backend stores all the data it needs to manage your infrastructure in a single file called the state file. It is a JSON file that links your Terraform configuration to the cloud resources it manages. When you run terraform plan and then terraform apply, Terraform creates the S3 bucket in your cloud account. It then records the bucket's ID, ARN, region, and other properties in the terraform.tfstate file.
Without the state file, Terraform cannot monitor:
Current resources and their state, such as an S3 bucket in your cloud account
Resource IDs and attributes such as ARNs and regions
Configuration drift, such as a manual change made outside of Terraform
Resource dependencies, such as a VPC that an EC2 instance relies on
Outputs from previous runs, such as IP addresses
If you lose your state file, you lose the ability to manage infrastructure with Terraform. You cannot detect mismatches, update resources, or remove them in a controlled way. Rebuilding the state file manually is possible, but time-consuming and risky.
AI Tutor Prompt
Core responsibilities of a Terraform backend
The two most common risks in any Terraform project are state corruption and data loss.
State storage: State storage defines where Terraform keeps your .tfstate file. The local backend stores state files on your local file system by default. It sits in the same directory as your Terraform code, right where you run your Terraform commands. As your team grows, you need to move the state file to a remote backend. AWS S3 backend, Google Cloud Storage, and Azure Blob Storage accounts are the most common options teams use. They give your team safe, shared access to your state files across all operations.
State locking: State locking protects your state data during active Terraform operations. When you enable state locking on your backend, Terraform places a lock on the state file. That lock blocks other processes from writing to it until the operation is complete.
You configure state storage and state locking through a backend block in your Terraform code. This block applies to self-managed remote backend types like S3, AzureRM, and GCS. HashiCorp Cloud Terraform uses a cloud block instead, which is a separate configuration path that the platform manages for you. Switching backend types without running terraform init -migrate-state leaves your state in the old location, disconnecting Terraform from the infrastructure it built.
Backend block vs. cloud block
Terraform gives you two ways to manage backend configuration:
Backend block: A backend block connects your project to a self-managed remote backend type, such as S3, AzureRM, or GCS. You control the storage, locking, and access configuration.
Cloud block: A cloud block connects your project to HCP Terraform as a managed platform. The platform handles storage, locking, and access control for you.
When you're working in HashiCorp Cloud workspaces, you don't need to use a backend block. Instead, Terraform Cloud manages your state file through a specific cloud block. Unlike standard remote backends that only handle state storage and locking, HCP Terraform also supports remote execution. This means terraform plan and terraform apply run on the HCP Terraform platform rather than on a local machine or in a CI/CD pipeline.
You may also see older guides refer to both the cloud and backend blocks as Standard Backends and Enhanced Backends, but HashiCorp dropped that distinction and replaced it with the backend and cloud block model.
Local vs. remote backend comparison
The backend type you choose determines how storage and locking work. The default local backend stores your state file on your machine. It has no locking support, which makes it a poor fit for production teams.
Remote backend offers both state storage and state locking to prevent file corruption. Production environments demand more state storage than a local backend can provide. A remote backend is the recommended option because it offers better durability, versioning, and backup features that keep your team safe from data loss.
The table below shows the differences between these two backend types:
Feature | Local backend | Remote backend (S3 bucket / AzureRM / GCS) |
|---|---|---|
Storage location | Local filesystem | Cloud object storage |
State locking | None | Yes (mechanism varies) |
Team safe | No | Yes |
Versioning and rollback | No | Yes (requires enabling on the storage service) |
Best suited for | Solo learning, demos | Production environments, Team workflows |
The local backend
When you start a new Terraform project, the local backend is your default backend. It stores your state file on your local filesystem (disk) with no backend configuration needed. This makes it a great fit for solo learning and quick demos. But the moment more than one person works on the same project, local backend storage becomes an issue.
The local backend tends to struggle in team environments because of these reasons:
The local backend has no state locking to stop overlapping operations from corrupting your state data.
Storing state files on a local filesystem gives your team no backup and no shared access to that data.
Your state file stores sensitive data like database passwords in plain text, and one Git commit can push that data to a public repository.
To avoid bad default settings, Terraform handles backend setup through terraform init. This command automatically generates a local backend configuration for your project. But teams often add one when they want a backend configuration file path or a custom local setup. When that happens, you declare the parameters using this format:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
AI Tutor Prompt
Remote backends: The right default for teams
Remote backends move your state data from local machines to secure cloud storage. Every team member and CI/CD pipeline gets access to the same state backend through a shared, central location. Common remote backend providers include Amazon S3, Azure Blob Storage, and Google Cloud Storage, each with a different setup. Each environment still needs its own backend configuration, or at least partial configuration values, before terraform init can connect to the central location.
Remote backend provides the following advantages for your team:
Multiple teams can work from the same state file without manual file sharing.
Remote storage providers offer backups and redundant storage to prevent data loss.
Terraform backends keep sensitive state data secure in transit and at rest.
Enable versioning on your storage service to keep a history of your state files. S3, GCS, and Azure Blob Storage all support versioning, but each one requires a separate setup step.
CI/CD pipelines can access state data in the cloud without storing sensitive credentials in your Terraform code. You still need to pass the required backend configuration values at
terraform inittime before the pipeline can connect.
To get these features, your team needs a secure place for storing Terraform state files. The best option depends on your chosen cloud:
S3 backend with native locking using the
use_lockfileparameter for AWS teams.AzureRM backend connecting to an Azure Blob Storage account with blob lease locking for Azure teams.
Terraform GCS backend uses Google Cloud Storage API mechanisms to provide built-in state locking for GCP teams.
HashiCorp Cloud Terraform handles state storage and locking as a managed platform. It replaced the legacy free tier in March 2026. The new enhanced free tier gives teams up to 500 managed resources and unlimited users at no cost.
HTTP, Kubernetes, and the Consul backend support self-hosted infrastructure in private data centers.
These backend types cover Terraform state management across different platforms. Your choice of deployment engine determines which backend configuration best fits your team. If your team moves to OpenTofu, most backend types, including S3, AzureRM, GCS, and HTTP, work the same way they do in Terraform. The HCP Terraform cloud block is the one backend type that does not transfer over to OpenTofu. It integrates with HashiCorp infrastructure and does not work outside the commercial platform.
AI Tutor Prompt
Configuring remote backends in Terraform
The following section shows how to configure the S3 backend, the most common remote backend for teams running their infrastructure on AWS. These examples use current syntax, including native S3 locking from Terraform 1.10. Terraform introduced this to replace the DynamoDB locking approach used in older versions.
AWS S3 backend
Before you start writing the AWS S3 backend block, you need:
A target S3 bucket to host your state file. Enable object versioning on this bucket as a recommended best practice to protect your data from accidental deletion.
Active AWS credentials before you attempt to initialize the Terraform backend. For local development, configure your environment variables (
AWS_ACCESS_KEY_ID,AWS_SECRET_ACCESS_KEY) to pass access keys. You can also manage access using local profiles inside the AWS shared credentials file and AWS shared configuration file. Then use a secure IAM role for automated environments, such as CI/CD pipelines, instead.IAM permissions (
s3:GetObject,s3:PutObject,s3:ListBucket) for S3 bucket access.
Terraform will not create the S3 bucket during Terraform initialization. You must create the bucket with object versioning enabled and configure your access first via the AWS CLI. If you use the default us-east-1 region, run this command:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
If you use any other AWS region, you must include the location constraint parameter to prevent API errors:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
Once you create the bucket, execute the following command to enable object versioning:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
Bucket versioning is not a strict requirement for the S3 backend to work. Some teams take that as a reason to skip it entirely during their first setup. But I've worked with teams that lost key state files after a failed deployment and had no way to recover them. So, make sure to enable object versioning before you do your first terraform init commit to protect against that risk.
Terraform 1.10 introduced native S3 locking via the use_lockfile = true argument. This feature removes the need to provision a separate Amazon DynamoDB table for state locking. If you use Terraform versions below 1.10, omit this new argument and use the traditional dynamodb_table parameter to enforce concurrency protection.
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
If your team uses Azure Blob Storage or Google Cloud Storage for infrastructure, you can use the same configuration setup for authentication and security measures specific to each provider.
Partial backend configuration
A partial configuration lets you omit sensitive values from your backend configuration file. Instead of saving these values in the text, you pass the missing values later at the terraform init time. Doing this helps complete the initialization of your remote state storage without issues.
Using partial configuration keeps credentials out of public repositories and reduces security exposure. It allows you to use different Terraform backend parameters across multiple workspaces.
To get started, define an empty backend block:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
Leaving the backend block empty keeps sensitive values out of your Terraform configuration. You provide the remaining backend details at terraform init time in one of two ways:
Inline flags
A backend config file
Use either method to supply the missing values and connect to your remote backend:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
Use the first method when your CI/CD pipeline stores values as environment variables. The second method uses -backend-config=PATH to pull values from a backend file. Keeping one file per environment makes managing multiple environments straightforward.
AI Tutor Prompt
Migrating between Terraform backends
At some point, managing infrastructure across many environments leads teams to change how they store state files. They might migrate from a local backend to an S3 backend, or from S3 to a managed backend.
But from what I've seen, teams that don't bother with backups are the ones that waste a lot of time fixing their state file when a migration goes wrong. So, it's important you back up your state file before you start, as one mistake can damage your remote state data.
The migration process
After working with Terraform backends for a while now, the first piece of advice I always give engineers before a migration is to prepare their state files and configuration before making any changes to their storage setup. The steps below will show you how to move your state file without data loss:
Step 1: Back up your current state data
Run the terraform state pull command to save a copy of your infrastructure state before you make changes. It gives you a recovery file if something goes wrong during the migration. Skipping this step can corrupt state data and lead to infrastructure management issues.
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
Step 2: Update the backend block
Open the main configuration file in your root module and locate the Terraform block. Change the backend config settings to point to your new cloud storage destination. This change centralizes your management when migrating from a local state file. To do this, replace the backend "local" {} block with the new backend "s3" {} block and fill in the required arguments. It's important to only have one backend block to avoid configuration errors in your setup. Updating this code changes only the configuration file. You still need to run the next initialization step to activate the new backend and transfer the data.
Step 3: Initialize the new backend
Run the initialization command to prepare your Terraform directory. This step activates your new backend configuration before Terraform moves any state data. Terraform then detects that your backend configuration now points to a new location. It prompts you to confirm whether you want to migrate your existing state data to the new remote backend.
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
Step 4: Confirm the migration
Type yes when the confirmation prompt appears in your terminal window. Terraform then copies your state data to the new remote backend destination. After the upload finishes, Terraform validates the transfer. It then checks that the new Terraform backend contains the same state data as the source file.
Step 5: Verify the change
Run the terraform plan command to confirm the migration worked. A clean plan with zero detected changes confirms that Terraform can read the migrated state file and that your real infrastructure resources match those in the new Terraform backend. If the plan shows unexpected changes, stop and do not apply. Pull up your backup state file and compare it with the migrated state to identify any gaps before you move forward.
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
For automated scripts and non-interactive environments, confirmation prompts block execution. Use the migration flags to continue the migration without user input:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
This migration process works the same way if you manage multiple workspaces. Terraform detects the workspace structures and prompts you to copy all existing workspaces to the new remote backend.
Migrating away from HCP Terraform (cloud block)
State migration happens when business needs change or when platform limits require a shift. For example, some teams outgrow the HashiCorp Cloud Terraform free tier. They then replace the platform cloud block with a traditional remote storage block like Amazon S3 or Google Cloud Storage.
The following steps outline how to perform this migration:
Step 1: Pull the remote state locally
Start by downloading your current state data from the cloud platform into your local root directory:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
The local-state.json file contains plain-text secrets and resource metadata. Handle this file securely, never commit it to your version control repository, and delete it immediately once you finish the migration.
Step 2: Configure the destination backend
Remove the cloud {} block from your configuration file. In its place, add a fully defined backend block for your target Amazon S3 storage bucket. You must supply your specific bucket, key, and region parameters so Terraform knows exactly where to route the data. Turn on native S3 locking by setting the use_lockfile parameter to true:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
Step 3: Authenticate and migrate
Establish your cloud provider credentials in your terminal session via environment variables or CLI configurations so Terraform can access the target bucket. Once authenticated, execute the initialization command with the migration flag to transfer your infrastructure state data:
Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
Terraform processes the transition and uploads your local state file into the new cloud bucket destination. This workflow ensures a secure state transfer across infrastructure engines. It also applies to OpenTofu migrations, which do not support the native HashiCorp Cloud platform block.
AI Tutor Prompt
Practical applications of Terraform backends
The following are examples of how teams use Terraform backends:
Team collaboration
Shared remote storage lets multiple teammates work on the same infrastructure code together. When state files sit on a local machine, other team members cannot see recent updates. A remote backend solves this by providing access to the infrastructure state from a single central location. It also uses state locking to block a second deployment command while the first one runs. This feature protects your work from corruption in both open-source Terraform and Terraform Enterprise.
Environment isolation
Use unique paths, prefixes, or cloud access keys for your configurations. This method keeps your development, staging, and production workloads separate, which limits your blast radius (how far damage can spread) in the event of a bad deployment. A mistake in development stays contained and cannot reach your production state files. Independent remote folders handle this separation without requiring a complex nested backend block.
CI/CD pipeline automation
Automated pipelines need a central storage mechanism to hold and manage remote state without human intervention. A remote backend fulfills this requirement by acting as the authoritative host for your infrastructure records. Your CI/CD jobs access this remote state by evaluating your backend configuration block and using credentials stored in environment variables.
When two pipeline jobs trigger at the same time, a backend with state locking blocks the second request until the first finishes. Backends without state locking lack this protection, which allows concurrent runs to cause conflicting updates and corrupt your infrastructure data.
State sharing
Large systems often require you to break infrastructure into small, manageable pieces. You can use the terraform_remote_state data source to link these smaller pieces together. This data source lets you access outputs from a separate backend state file snapshot. One infrastructure configuration exposes resource data as outputs, and an independent configuration then consumes those values to build dependent resources.
For example, a networking configuration sets up a Virtual Private Cloud (VPC) and writes the subnet IDs to its remote state file as outputs. An application configuration reads those subnet IDs through terraform_remote_state and uses those data source attributes to deploy its virtual machines to the correct network.
Disaster recovery
Cloud object storage, such as Amazon S3, offers built-in backup tools for your files. When you enable object versioning, it keeps a history of all changes made to a state file. So, a bad configuration change no longer means a full infrastructure rebuild. You can view the version history and restore a previous state file to bring your system back to a known good state.
AI Tutor Prompt
Common errors with Terraform backends
The following are common issues I've seen teams face when using Terraform backends:
State file committed to Git: If a state file is committed to Git, plain-text secrets are now in your version control repository. Add state patterns to your
.gitignorefile now to protect your data.Missing backend storage: Initialization fails because the target bucket does not exist. Build your cloud storage assets first using a bootstrap script or the cloud CLI before you run Terraform.
Switching backend connections: Initialization fails when you modify a backend block because your local configuration no longer matches the state from your last initialization. Use the
-reconfigureflag to discard the previously cached backend metadata and force Terraform to initialize using the backend configuration currently in your files. However, only use-reconfigurewhen you do not need to move your existing state to the new backend. If you need to keep state data, use-migrate-stateinstead. Running-reconfigurewhen state migration was your intention will leave your existing state behind in the old backend.Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
plaintextMigrating existing state data: When you move your infrastructure history from local storage to a new remote destination, Terraform requires explicit confirmation. Use the
-migrate-stateflag to copy your existing state tracking data over to the new backend:Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
plaintextState lock not released: A pipeline crash or a sudden network loss leaves the state file locked. Run the
force-unlockcommand with the unique ID provided in the error message to clear the block:Auto—ArduinoBashCCppCsharpCssDiffGoGraphqlIniJavaJavascriptJsonKotlinLessLuaMakefileMarkdownObjectivecPerlPhpPhp-templatePlaintextPythonPython-replRRubyRustScssShellSqlSwiftTypescriptVbnetWasmXmlYaml1cAbnfAccesslogActionscriptAdaAngelscriptApacheApplescriptArcadeArmasmAsciidocAspectjAutohotkeyAutoitAvrasmAwkAxaptaBasicBnfBrainfuckCalCapnprotoCeylonCleanClojureClojure-replCmakeCoffeescriptCoqCosCrmshCrystalCspDDartDelphiDjangoDnsDockerfileDosDsconfigDtsDustEbnfElixirElmErbErlangErlang-replExcelFixFlixFortranFsharpGamsGaussGcodeGherkinGlslGmlGoloGradleGroovyHamlHandlebarsHaskellHaxeHspHttpHyInform7Irpf90IsblJboss-cliJuliaJulia-replLassoLatexLdifLeafLispLivecodeserverLivescriptLlvmLslMathematicaMatlabMaximaMelMercuryMipsasmMizarMojoliciousMonkeyMoonscriptN1qlNestedtextNginxNimNixNode-replNsisOcamlOpenscadOxygeneParser3PfPgsqlPonyPowershellProcessingProfilePrologPropertiesProtobufPuppetPurebasicQQmlReasonmlRibRoboconfRouterosRslRuleslanguageSasScalaSchemeScilabSmaliSmalltalkSmlSqfStanStataStep21StylusSubunitTaggerscriptTapTclThriftTpTwigValaVbscriptVbscript-htmlVerilogVhdlVimWrenX86asmXlXqueryZephir
plaintextVerify that no other automated process runs before you execute this command.
Pipeline crashes: Concurrent automation jobs conflict when lock checks are missing. Configure your CI/CD pipeline to queue jobs or wait for open locks to clear, rather than failing your build.
Best practices with Terraform backends
In my experience, every Terraform backend failure in production comes down to skipping one of these practices:
Hardcoded credentials in configuration: Never put secret keys or access tokens inside your backend blocks. The backend configuration should only define the storage destination, not your authentication data. Instead, authenticate remote backend access via your cloud environment or a CI/CD runner identity. Terraform resolves these storage credentials at runtime using system environment variables or cloud provider authentication flows.
State exposure in Git history: When you test configurations locally, Terraform creates cache files before pushing data to your remote destination. These files contain plain-text passwords and sensitive resource parameters. To keep this temporary local data out of your version control history, place
.tfstate,.tfstate.backup, and.terraform/inside your.gitignorefile.Data loss from state corruption: Accidental deletion ruins your infrastructure history. Enable object versioning on backend storage buckets to allow fast rollbacks. Versioning gives you a recovery path for your state files and local metadata after corruption or accidental deletion.
Unencrypted state file leaks: Plain-text state files expose passwords to unauthorized users. Protect this data at rest and in transit across all storage platforms. Do not rely on
encrypt = truein the backend block as your only encryption measure. Instead, configure server-side encryption (SSE) on your cloud bucket settings. AWS S3 enforces base bucket encryption by default, but you should combine this mechanism with an AWS Key Management Service (KMS) customer-managed key for enterprise protection.Massive blast radius risks: Mistakes in development workspaces can damage production resources. Separate state files per environment and per logical tier. Restrict workspace access boundaries to enforce safety.
Conclusion: Set up your backend and keep building
The local backend is a good starting point for solo learning and small experiments. It does not support a production infrastructure workflow. As soon as your project involves more than one person or a CI/CD pipeline, you need a remote backend. Cloud options like S3, AzureRM, and GCS support remote storage and state locking.
While you enable features like object versioning and encryption at rest directly on the underlying cloud storage service, your Terraform backend configuration block works in tandem with these services to manage your state deployment rules. HashiCorp Cloud Terraform adds a managed control plane on top of your storage, but this platform shifts to a paid model once you exceed 500 resources.
A well-configured backend is the foundation of a stable Terraform project. When you get it right from the start, the rest of your Terraform workflow runs without issues. Sign up to roadmap.sh and get access to the Terraform Roadmap and Backend Development Roadmap for a structured learning path. You can also try the AI Tutor for help with backend setup and troubleshooting. Your account tracks your progress and gives you on-demand access to AI-powered explanations as you learn.
AI Tutor Prompt
Related Guides
Related Guides TechnologyNoneFrontend BeginnerBackend BeginnerDevOps BeginnerGit and GitHub BeginnerClaude CodeVibe CodingOpenClawLeetCodePythonPython for Data AnalysisComputer ScienceSQLReactVueFrontendBackendFull StackDevOpsDevSecOpsData AnalystAngularAI EngineerAI and Data ScientistData EngineerAndroidMachine LearningPostgreSQLJavaScriptiOSTypeScriptNode.jsBlockchainQASystem DesignSoftware ArchitectJavaASP.NET CoreAPI DesignSpring BootFlutterC++RustGoCyber SecurityAI Product BuildersUX DesignSoftware Design and ArchitectureGraphQLReact NativeDesign SystemPrompt EngineeringMongoDBLinuxKubernetesDockerAWSTerraformTechnical WriterGame DeveloperServer Side Game DeveloperMLOpsData Structures & AlgorithmsRedisProduct ManagerGit and GitHubPHPEngineering ManagerDeveloper RelationsCloudflareBI AnalystAI Red TeamingAI AgentsNext.jsCode ReviewKotlinHTMLCSSSwift & Swift UIShell / BashLaravelAWSAPI SecurityBackend PerformanceFrontend PerformanceCode ReviewElasticsearchWordPressDjangoRubyRuby on RailsScalaDesigning Data Intensive AppsOctopus AINetwork EngineerForward Deployed EngineerC Programming
Related Guides Count
Kamran Ahmed