Terminus by Warp
Run a Shell Script in a Dockerfile

Run a Shell Script in a Dockerfile

Preston Tunnell Wilson
Preston Tunnell Wilson
Senior Software Engineer, InStride Health

[#the-short-answer]The Short Answer[#the-short-answer]

[.inline-code]COPY[.inline-code] the shell script that you want to run into the container and then either [.inline-code]RUN[.inline-code] it if you want to execute it while you build the container:

Or use it in a [.inline-code]CMD[.inline-code] instruction if the script executes the main functionality of your Docker container:

[#when-to-run-a-shell-script-in-docker]When might you want to run a shell script in your Dockerfile?[#when-to-run-a-shell-script-in-docker]

Maybe you are migrating an old bash script that sets up a virtual machine or other OS environment to set up a Docker image instead. Maybe you’re trying to reduce the size of your images by combining all of your Docker commands into one shell script to run (though a multi-stage build might be more beneficial). Or maybe your command to run in Docker is actually or is started by a shell script. Depending on your use case, you will run your shell script in your Dockerfile slightly differently.

The three basic steps are:

  1. [.inline-code]COPY[.inline-code]ing the shell script into the Docker image through the Dockerfile, then either:
  2. [.inline-code]RUN[.inline-code]ning the script or
  3. listing the script as the default command to run in the container with [.inline-code]CMD[.inline-code] 

[#copying-the-script]Step 1: [.inline-code]COPY[.inline-code]ing the script over[#copying-the-script]

After we have selected the Docker image on which to base our final image and installed packages, but before we use our script, we have to copy it over! Say that the script that we want to copy is in a child directory [.inline-code]scripts[.inline-code] from where we are building our container and that the script name is [.inline-code]setup.sh[.inline-code]. To copy it over, we would run

 …
 COPY scripts/setup.sh /opt/src/scripts/setup.sh
 …

For more detail on copying files over to Docker, check out the official [.inline-code]COPY[.inline-code] documentation.

Note: Dockerfiles also support a command called [.inline-code]ADD[.inline-code], which is similar to [.inline-code]COPY[.inline-code]. However, it behaves in a potentially unexpected way; for example, [.inline-code]ADD[.inline-code] automatically unpacks [.inline-code]gzip[.inline-code]ped files. To avoid those unexpected surprises, I recommend using [.inline-code]COPY[.inline-code] for copying files and directories into a Docker image. (For more detail on [.inline-code]ADD[.inline-code], please check out the official [.inline-code]ADD[.inline-code] documentation.)

[#running-script-while-building-docker-image]Step 2: [.inline-code]RUN[.inline-code]ing the script while building the Docker image[#running-script-while-building-docker-image]

If we need to execute that script as part of building the Docker image, we need to [.inline-code]RUN[.inline-code] it!

 …
 RUN /opt/src/scripts/setup.sh
 …

Any parameters we want to pass in go after the name of the script. For example: [.inline-code]RUN /opt/src/scripts/setup.sh --environment dev[.inline-code].

If you want more examples or more options, there are many more details in the official [.inline-code]RUN[.inline-code] documentation

One note is that if you are consolidating a bunch of Dockerfile instructions into one shell script to reduce the number of intermediate images, thus trimming down total Docker container sizes and total time to build, structuring your Dockerfile into a multi-stage build might be more beneficial than using a script. See the official documentation for more details, but the gist is that we separate out several sets of Dockerfile instructions into different images for better Dockerfile instruction caching and a smaller final image that contains only what the container needs to run.

[#need-to-run-bash-script-or-command-in-dockerfile]But I need to run a [.inline-code]bash[.inline-code] script or command in my Dockerfile![#need-to-run-bash-script-or-command-in-dockerfile]

The default shell for Linux-based containers is plain [.inline-code]sh[.inline-code]. If you need to run specifically with [.inline-code]bash[.inline-code], you can either change the shell executing the commands with [.inline-code]SHELL[.inline-code] or call out to [.inline-code]bash[.inline-code] directly in the [.inline-code]RUN[.inline-code] statement:

 SHELL [“/bin/bash”, “-c”]
 # or 
 RUN /bin/bash -c '/opt/src/scripts/setup.sh’'

The [.inline-code]SHELL[.inline-code] command is most useful if you are working in Windows, which has two different shells, or if you need many [.inline-code]bash[.inline-code] scripts executed in your Dockerfile. For more detail, check out the official [.inline-code]SHELL[.inline-code] documentation.

[#what-is-bracket-syntax-used-for-shell]What is that bracket syntax you used for [.inline-code]SHELL[.inline-code]?[#what-is-bracket-syntax-used-for-shell]

[.inline-code]SHELL [“/bin/bash”, “-c”][.inline-code] is an example of the “exec-form” of running commands in Docker. This is in contrast to the “shell form” that we have been using in this article. In most cases, exec-form is recommended, but it behaves somewhat unexpectedly compared to running commands in a local terminal because it does not interpolate string variables, which can be common when running shell scripts.

For example, if you are using something like [.inline-code]$PWD[.inline-code] in your [.inline-code]RUN[.inline-code] command, use the syntax that we have been using. The official [.inline-code]RUN[.inline-code] documentation covers this in more detail.

[#setting-a-script-as-default-cmd-for-image]Step 3: Setting a script as the default [.inline-code]CMD[.inline-code] to run for your image[#setting-a-script-as-default-cmd-for-image]

The main purpose of a [.inline-code]CMD[.inline-code] instruction is to be a default program to execute when running your container. I prefer to be explicit and clear, especially with Docker which has so many options available, but if your container is really only used for one purpose, you can set it as the default in [.inline-code]CMD[.inline-code]:

 …
 CMD /opt/src/scripts/start-server.sh

I don’t normally use this, but I am pointing it out for completeness’s sake. The official documentation for [.inline-code]CMD[.inline-code] lists more options and goes into the [.inline-code]ENTRYPOINT[.inline-code] instruction, which is related to the [.inline-code]CMD[.inline-code] instruction.

Dockerfiles have a lot of potential gotchas and places where small changes can have big speed and size implications. Docker has some great recommendations and best practices to aid you in your iteration process. I recommend starting reading at the [.inline-code].dockerignore[.inline-code] section.

Experience the power of Warp

  • Write with an IDE-style editor
  • Easily navigate through output
  • Save commands to reuse later
  • Ask Warp AI to explain or debug
  • Customize keybindings and launch configs
  • Pick from preloaded themes or design your own
brew install --cask warp
Copied!
Join the Windows waitlist:
Success! You will receive an email from Warp when the release is available to download.
Oops! Something went wrong while submitting the form.
Join the Linux waitlist:
Success! You will receive an email from Warp when the release is available to download.
Oops! Something went wrong while submitting the form.
Join the Linux waitlist or join the Windows waitlist
Join the Windows waitlist:
Success! You will receive an email from Warp when the release is available to download.
Oops! Something went wrong while submitting the form.