Docker
Dockerfile
Scripting
Automation
DevOps

Run a script in Dockerfile

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

Introduction

Running a script from a Dockerfile can mean two different things: executing a script while the image is being built or arranging for a script to run when a container starts. Those are different phases with different instructions. The key is to choose RUN for build-time actions and CMD or ENTRYPOINT for container startup behavior.

Build-Time Script Execution With RUN

If the script installs packages, prepares files, or compiles assets into the image, execute it during the build with RUN.

A common pattern is to copy the script into the image, make it executable, and then run it:

dockerfile
1FROM ubuntu:24.04
2
3WORKDIR /app
4COPY scripts/setup.sh /app/setup.sh
5RUN chmod +x /app/setup.sh
6RUN /app/setup.sh

An example setup.sh might be:

bash
1#!/usr/bin/env bash
2set -euo pipefail
3apt-get update
4apt-get install -y curl
5rm -rf /var/lib/apt/lists/*

Everything done by RUN becomes part of the image layer produced at build time.

Runtime Script Execution With CMD or ENTRYPOINT

If the script should execute when the container starts, use CMD or ENTRYPOINT instead.

dockerfile
1FROM python:3.12-slim
2
3WORKDIR /app
4COPY start.sh /app/start.sh
5RUN chmod +x /app/start.sh
6ENTRYPOINT ["/app/start.sh"]

And the script:

bash
#!/usr/bin/env bash
set -euo pipefail
python main.py

This script runs each time a container starts from the image. That is very different from RUN, which executes only when you build the image.

When to Use Which Instruction

Use RUN when the script:

  • installs dependencies
  • generates build artifacts
  • prepares the filesystem inside the image
  • should not run again every time the container starts

Use CMD or ENTRYPOINT when the script:

  • launches the application
  • performs startup initialization
  • reads runtime environment variables
  • must execute every time a container is started

A simple mental model is: RUN changes the image, while CMD and ENTRYPOINT define container behavior.

Prefer Small, Transparent Scripts

Scripts inside Docker builds should usually be short and predictable. If the logic is only one or two shell lines, writing it directly in the Dockerfile can be clearer:

dockerfile
RUN apt-get update && apt-get install -y curl && rm -rf /var/lib/apt/lists/*

Use an external script when it improves readability, is reused elsewhere, or contains enough logic that embedding it would make the Dockerfile harder to maintain.

Also keep line endings in mind. Shell scripts copied from Windows environments often fail in Linux containers because of carriage-return characters. If a script inexplicably will not execute, check the file format.

Exec Form Is Usually Better

For CMD and ENTRYPOINT, prefer the JSON exec form:

dockerfile
ENTRYPOINT ["/app/start.sh"]

This avoids an extra shell layer and gives cleaner signal handling. Good signal handling matters because containers should stop cleanly when they receive termination signals.

If your startup script launches the main process, use exec inside the script so the target process becomes PID 1:

bash
#!/usr/bin/env bash
set -euo pipefail
exec python main.py

That small detail improves shutdown behavior a lot.

A Full Example

dockerfile
1FROM node:22-alpine
2
3WORKDIR /app
4COPY package*.json ./
5RUN npm ci
6
7COPY . .
8RUN chmod +x /app/scripts/build-assets.sh
9RUN /app/scripts/build-assets.sh
10
11RUN chmod +x /app/scripts/start.sh
12ENTRYPOINT ["/app/scripts/start.sh"]

This image uses one script during the build to prepare assets and another at runtime to start the service.

Common Pitfalls

The most common mistake is using RUN for something that should happen when the container starts. That script runs only during image build, so the expected runtime behavior never appears.

Another mistake is forgetting to copy the script into the image or forgetting to set executable permissions.

Developers also often use shell-form CMD or ENTRYPOINT without realizing it changes signal handling and argument parsing.

Finally, startup scripts should usually end with exec when they launch the main process. Without it, container shutdown can become awkward and less predictable.

Summary

  • Use RUN to execute scripts during image build.
  • Use CMD or ENTRYPOINT to execute scripts when a container starts.
  • Copy scripts into the image and mark them executable.
  • Prefer exec-form ENTRYPOINT or CMD for cleaner runtime behavior.
  • Keep scripts small and use exec in startup scripts when launching the main process.

Related reading
Course
Beginner
27 lessons
10 hours
System Design Fundamentals

Build a strong foundation in designing scalable, reliable distributed systems.

View the course
Track what you have practised

A free account saves your progress, solutions and study plan across every problem on Codemia.

System Design practice on Codemia

Work through 120+ system design problems with detailed solutions, from rate limiters to multi-region storage.

Practice system design

All Rights Reserved.