Docker vs Terraform Interview Questions: Containers vs Infrastructure as Code

Reviewed by Mark Dickie · Last updated

Docker and Terraform are both core tools in modern DevOps workflows, but they solve different problems: Docker packages and runs applications inside containers, while Terraform declaratively provisions and manages infrastructure across cloud providers. For an interview, the two rarely compete directly — a question about Docker image layering is a completely different muscle from one about Terraform state management — yet engineers increasingly need a working grasp of both, and interviewers know it. Candidates who treat them as interchangeable "DevOps things" tend to stumble on precisely the questions that probe the boundary: when do you reach for a Dockerfile versus a Terraform resource block, and what breaks if you confuse the two?

How they differ as interview subjects

DockerTerraform
Core concept testedContainer lifecycle, image builds, networking, volumesInfrastructure state, provider config, plan/apply cycle
Typical question styleDebugging scenarios, Dockerfile optimisation, runtime flagsDeclarative syntax, state file handling, module design
Where candidates drop marksLayer caching rules, multi-stage builds, inter-container networkingState drift, remote backends, destroy vs taint semantics
Closest real-world roleApplication packaging, local dev, CI pipelinesCloud provisioning, platform engineering, IaC at scale
Difficulty curveEntry questions are accessible; depth comes from internalsConceptual floor is higher; state management trips many mid-levels

How to decide which to focus on

  1. Check the job description first. A backend engineer role will weight Docker heavily; a platform or SRE role almost always expects Terraform fluency. Study the one in the title, then shore up the other.
  2. Know your own gaps honestly. If you have run docker run daily but never written a terraform plan, your Terraform answers will read as surface-level. Interviewers hear it quickly.
  3. Prepare the overlap zone. Questions about running Terraform inside a Docker container, or about managing container registries with Terraform, appear at mid-to-senior level. Expect at least one.
  4. Match depth to seniority. Junior interviews tend to stay on Docker basics and Terraform syntax. Senior rounds go into multi-stage build strategy, remote state locking, and workspace design — be ready to discuss trade-offs, not just commands.
  5. Use the decision table below. The attempt data on this page shows which specific questions candidates miss most often in each technology. That miss-rate pattern is a more direct study guide than any syllabus.

Docker vs Terraform, side by side

How Docker and Terraform compare on Tarmac’s interview questions.

MetricDockerTerraform
Practice questions66
Average score——
Hardest question (% who miss it)——
Average time per question——

Practice questions

You need to copy a local requirements.txt from the build context into the image. Docker's own best-practice guidance says to prefer one instruction here. Which, and why?#

Options

  • COPY, because it only copies local files and does nothing implicit — ADD also fetches URLs and auto-extracts archives, which is surprising
  • ADD, because COPY cannot copy a single file, only whole directories
  • ADD, because COPY does not preserve file permissions
  • Either is identical; COPY is just a newer alias for ADD
Show answer

Prefer COPY for copying local files into an image. COPY only copies files and directories from the build context, so its behaviour is transparent and predictable. ADD does extra implicit work — it can fetch remote URLs and auto-extracts local tar archives — which makes builds surprising. Both preserve permissions and handle single files, so reserve ADD for the narrow case where you actually want local-archive extraction.

Why:

Docker's Dockerfile best practices recommend COPY for plain file copies because its behaviour is transparent: it only copies local files/directories from the build context. ADD does extra implicit work — it can download remote URLs and will auto-extract local tar archives — which makes builds less predictable and can mask surprises. Both preserve permissions and both handle single files. Reaching for ADD 'just in case' is the misconception; use ADD only when you specifically want local-archive auto-extraction.

What is the primary purpose of the Terraform state file (terraform.tfstate)?#

Options

  • It maps the resources in your configuration to the real-world infrastructure objects Terraform manages
  • It stores your provider credentials so you don't re-enter them each run
  • It is a human-authored file where you declare the desired infrastructure
  • It caches downloaded provider plugins to speed up terraform init
Show answer

The state file maps each resource address in your configuration to the real infrastructure object Terraform created (an instance id, a bucket ARN). Terraform reads it to compute the diff between desired and actual state, so it knows what to create, update, or destroy. It is not where you declare desired infrastructure (that's your .tf files), and it is not a credential or plugin cache.

Why:

State is Terraform's record of which real object (an AWS instance id, a bucket ARN) corresponds to each resource address in your config. Without it Terraform couldn't tell what already exists, so it couldn't compute a diff or know what to update or destroy. Credentials are supplied by the provider config/environment, the desired state is your .tf files, and plugins are cached under .terraform/. Losing or corrupting state is what makes Terraform try to recreate or orphan live infrastructure.

Your Dockerfile has EXPOSE 8080. You run the container with docker run myimg (no -p). Can a client on the host reach the app on port 8080?#

Options

  • No — EXPOSE is only metadata; you must publish the port with -p 8080:8080 for host traffic to reach the container
  • Yes — EXPOSE automatically publishes the port to the host
  • Yes, but only from other containers on the same network, never from the host
  • No — EXPOSE blocks the port unless you also add --publish-all
Show answer

No. EXPOSE is only metadata — it records which port the application listens on but never publishes it. For host traffic to reach the container you must publish the port explicitly with -p 8080:8080, or auto-publish all exposed ports with -P. Publishing with -p even works without an EXPOSE line. Assuming EXPOSE opens the port is a common cause of an unreachable container.

Why:

EXPOSE is documentation/metadata: it records which port the app listens on but never publishes anything. To let host traffic reach the container you publish the port with -p 8080:8080 (or auto-publish all exposed ports with -P). You don't even need EXPOSE to publish — -p works regardless. The trap is assuming EXPOSE opens the port; teams hit this when a 'working' image is unreachable in production because nobody published the port.

Which of the following are valid arguments inside a resource's lifecycle {} block? Select all that apply.#

Options

  • create_before_destroy
  • prevent_destroy
  • ignore_changes
  • depends_on
Show answer

The lifecycle block accepts create_before_destroy (reverse the replace order to avoid downtime), prevent_destroy (reject plans that would destroy the resource), and ignore_changes (ignore drift on listed attributes), along with replace_triggered_by. depends_on is also a meta-argument but belongs at the resource level, not inside lifecycle. Placing it inside lifecycle is a frequent mistake.

Why:

create_before_destroy, prevent_destroy, and ignore_changes (plus replace_triggered_by) are the lifecycle meta-arguments: they tune replacement order, block destroys, and tell Terraform to ignore drift on chosen attributes. depends_on is a meta-argument too, but it sits at the resource level, not inside lifecycle — putting it there is a common authoring error. Knowing what lifecycle controls is what lets you do a zero-downtime replacement or stop an external autoscaler's changes from showing up as perpetual drift.

Why does Docker recommend the exec form ENTRYPOINT ["node", "server.js"] over the shell form ENTRYPOINT node server.js for a long-running service?#

Options

  • Exec form runs the process directly as PID 1, so it receives signals like SIGTERM and can shut down gracefully; shell form wraps it in /bin/sh -c, which becomes PID 1 and often doesn't forward signals
  • Exec form is faster because it skips parsing the Dockerfile
  • Shell form cannot pass any arguments to the process
  • Exec form automatically restarts the process if it crashes
Show answer

Exec form launches your process directly so it becomes PID 1 and receives signals — including the SIGTERM that docker stop sends — letting it shut down gracefully. Shell form runs the command through /bin/sh -c, so the shell becomes PID 1 and often fails to forward SIGTERM to your process; docker stop then waits the grace period and SIGKILLs it. That is the usual cause of containers that take ten seconds to stop.

Why:

With exec form, your process is launched directly and becomes PID 1, so it receives signals (notably the SIGTERM docker stop sends) and can drain connections and exit cleanly. Shell form runs /bin/sh -c "node server.js", making the shell PID 1; the shell frequently doesn't forward SIGTERM to its child, so docker stop hangs for 10s and then SIGKILLs — abrupt termination, dropped requests. Shell form does pass args and isn't slower; it just breaks signal handling. This is the classic 'my container takes 10 seconds to stop' bug.

Marking an output with sensitive = true encrypts that value so it is no longer stored in plaintext in the state file.#

Options

  • True
  • False
Show answer

False. sensitive = true only hides a value from CLI output, showing <sensitive> in plan and apply logs; the value is still stored in plaintext in the state file, and terraform output -json or -raw reveals it. Keeping a value out of state requires an ephemeral output. Real protection comes from encrypting the backend and restricting access to state, not from the sensitive flag.

Why:

False. sensitive = true only redacts the value from CLI plan/apply output (it shows as <sensitive>); the value is still written to state in plaintext, and terraform output -json/-raw will print it. Anyone who can read the state file can read the secret. To keep a value out of state entirely you use an ephemeral output (Terraform 1.10+). Believing sensitive encrypts state is a dangerous assumption — protect state with backend encryption and access controls instead.

What is the difference between docker run and docker exec?#

Options

  • docker run creates and starts a new container from an image; docker exec runs a command inside an already-running container
  • docker run runs in the foreground; docker exec runs the same container in the background
  • docker run is for images and docker exec is for images too, but exec skips the entrypoint
  • They are aliases; exec is the older name kept for compatibility
Show answer

docker run <image> creates a new container from an image and starts its main process. docker exec <container> <command> runs an extra command inside a container that is already running — so the target must already exist and be up; you cannot exec into a stopped container. exec is the tool for debugging a live container (docker exec -it web sh) without disturbing its main process.

Why:

docker run <image> creates a brand-new container from an image and starts its main process. docker exec <container> <cmd> runs an additional command inside a container that is already running — the target must exist and be running, so you can't exec into a stopped or nonexistent container. You use exec for debugging (docker exec -it web sh) without disturbing the main process. Confusing the two leads people to spin up a fresh container when they meant to inspect a live one.

Given this locals block, what is the resulting value of local.tags (evaluated with Terraform's merge function)?#

locals {
  defaults = { env = "dev", team = "core" }
  overrides = { env = "prod" }
  tags = merge(local.defaults, local.overrides)
}

Options

  • { env = "prod", team = "core" }
  • { env = "dev", team = "core" }
  • { env = "dev", env = "prod", team = "core" }
  • An error: duplicate key env across the merged maps
Show answer
`{ env = "prod", team = "core" }`
Why:

merge combines maps left to right, and when the same key appears more than once the value from the later argument wins. So env from overrides (prod) overrides env from defaults (dev), while team is carried through unchanged. Duplicate keys across arguments are not an error — last-wins is the whole point. This precedence is the standard pattern for a base tag/config map overridden per environment; getting the argument order backwards silently applies the wrong values.

A container writes a file to its own filesystem (not a volume). You then docker rm the container and start a fresh one from the same image. Is the file there?#

Options

  • No — writes go to the container's thin writable layer, which is discarded when the container is removed; the image's read-only layers are unchanged
  • Yes — writes are committed back into the image automatically
  • Yes — the writable layer is shared across all containers from that image
  • Only if the file is smaller than the image's layer size limit
Show answer

No. A container's writes go to a thin writable layer added on top of the image's read-only layers via copy-on-write. Removing the container discards that writable layer, so the file is lost, and the image itself was never modified — a fresh container starts clean. Writes are not committed back into the image, and each container has its own writable layer. Durable data must live in a volume or bind mount.

Why:

An image is a stack of read-only layers. Starting a container adds a thin writable layer on top (copy-on-write): all changes the container makes live only there. Removing the container deletes that writable layer, so the file is gone, and the image's layers were never modified — a new container starts clean. Writes are never committed back to the image automatically, and each container gets its own writable layer (they aren't shared). This is exactly why durable data must go in a volume or bind mount, not the container filesystem.

This configuration fails with a Self-referential block / cycle error during terraform plan. Which line is the cause?#

1| resource "aws_security_group" "web" {
2|   name = "web-sg"
3|   ingress {
4|     from_port       = 443
5|     to_port         = 443
6|     protocol        = "tcp"
7|     security_groups = [aws_security_group.web.id]
8|   }
9| }

Options

  • Line 7 — the resource references its own .id in its arguments, which Terraform cannot resolve because the id isn't known until the resource is created
  • Line 2 — name is a reserved attribute and cannot be set on a security group
  • Line 3 — an ingress block must be a separate aws_security_group_rule resource
  • Line 6 — protocol must be a number, not the string "tcp"
Show answer

Line 7 — the resource references its own .id in its arguments, which Terraform cannot resolve because the id isn't known until the resource is created

Why:

Line 7 makes the resource depend on its own computed id, which doesn't exist until the resource is created — a dependency cycle Terraform's graph rejects. To allow a security group to reference itself you set self = true on the rule instead of listing its own id. name is settable, inline ingress blocks are valid (a separate aws_security_group_rule is just an alternative), and protocol = "tcp" is correct. Self-reference cycles are a classic Terraform graph error that the message can make cryptic.

You need a private registry token available only while installing dependencies during the build, and it must NOT end up readable in the final image. Select every approach that leaks the token into the image's layers or history.#

Options

  • Pass it via ARG TOKEN and reference $TOKEN in a RUN — its value is visible in docker history
  • Set it with ENV TOKEN=... in the Dockerfile so the install step can read it
  • Write it to a file in one layer and rm the file in a later layer
  • Mount it with RUN --mount=type=secret,id=token (BuildKit), reading it only during that step
Show answer

ARG and ENV values are baked into image metadata and recoverable with docker history --no-trunc, and ENV also persists into every container's environment. Writing a secret then rm-ing it later does not help either: layers are immutable and retained, so the file survives in the earlier layer. The only safe approach is a BuildKit secret mount (RUN --mount=type=secret), which exposes the value to one RUN step and never writes it to any layer.

Why:

ARG and ENV values are baked into image metadata and are recoverable with docker history --no-trunc (and ENV also persists into every running container's environment). Writing the secret then rm-ing it in a later layer doesn't help: each layer is immutable and retained, so the earlier layer still contains the file even though the final filesystem hides it — anyone can extract it from the layer. The only safe option is a BuildKit secret mount (RUN --mount=type=secret), which exposes the secret to a single RUN and never writes it to any layer. Believing a later rm 'deletes' a secret is the most dangerous misconception here.

Your team currently keeps terraform.tfstate on each engineer's laptop. Explain why you'd move to a remote backend (e.g. S3, Terraform Cloud) and what two problems it solves for a team.#

Show answer

A local state file means each engineer has their own copy, so the state diverges and is never authoritative; it also isn't backed up and can be lost or committed to git with secrets in it. A remote backend stores a single shared state in a central, durable location so the whole team plans and applies against the same source of truth. Crucially it enables state locking: while one apply holds the lock, others are blocked, preventing two people from writing state at once and corrupting it. It also gives durability/versioning (S3 versioning, backups) and keeps the (plaintext) state off laptops behind access controls and encryption at rest.

Why:

The two big wins are a single shared source of truth (no divergent laptop copies) and state locking to prevent concurrent applies from corrupting state. Strong answers also mention durability/versioning and that state holds secrets in plaintext, so a remote backend with encryption and access control is a security improvement too. This is the question that separates someone who has run Terraform solo from someone who has run it on a team.

Keep reading

What moved, monthly

One email a month when the bulletin comes out: what moved in the markets we track, and the new question topics we published. Confirm your address to join. Unsubscribe any time.