Skip to content
Chrissy LeMaire edited this page Sep 15, 2022 · 9 revisions

docker

What an adventure! Seven days ago, I decided to build our Docker images the proper way, using docker compose or docker build.

How did I do it previously?

Let me say first that the non-proper way I did initially it was more than good enough. I even got kudos from people I think are super cool, like Steph Locke! So if you are just diving into containers, keep diving in the way that works for you.

The way I did it previously, however, ended up creating a much larger base image than it needed to be, adding about 600MB total. That's because I:

  • Pulled a SQL Server 2017 container
  • Added a bunch of things, like sample databases such as pubs and Northwind, fake logins, common agent jobs and more
  • Tested if they worked with our limited (at the time) Linux support
  • Figured out how certificate authentication worked in SQL Server using these Linux containers
  • Dropped the things that didn't work as to avoid disappointing people
  • Added, tested, and dropped even more things
  • Finally got it right after several days
  • Committed all of that to the container, resulting in a 600MB container layer
  • Pushed it out to Docker Hub then wrote a blog post about it

While this method worked well enough, Shawn Melton suggested I do it in a repeatable way using docker compose. By that time, however, I'd moved on to something else and decided to add that task to my todo list.

It stayed there, for four years, until last week when our database certificate expired and our tests started failing. Instead of doing just a quick fix, I figured now was finally a good time to learn Docker Compose 🐳

That was fun

I've learned a ton along the way and even added support for platform combinations that didn't exist back in 2017, like SQL Server and ARM.

Here are the highlights of things I learned in the past week:

  • You can easily serve multiple architectures under the same tag (dbatools/sqlinstance:latest), like x86, x64 and ARM.
  • Everything you do on a container before pushing it out to Docker Hub increases the size, including deleting a file because that instruction is kept in a layer. This makes it easy to bloat an image but there are ways around that.
  • SQL Server Edge, which runs on ARM systems like Apple M1 and Raspberry pi, doesn't support sqlcmd yet but it does support PowerShell and go.
  • /var/opt/mssql/mssql.conf is awesome and can replace a lot of manual config settings.
  • Using COPY is safer than ADD, but ADD is used a lot in tutorials.
  • The last command in a Dockerfile should be an ENTRYPOINT and not a CMD. It makes it easier for end-user to interact in a way that I don't understand just yet.
  • docker-compose is out and docker compose is in, but docker compose only works on systems that support Docker Desktop for now. This means it is not yet available on Linux.
  • Environmental variables don't work inside the container build, so you need to import them
  • Environmental variables do, however, show up in the ENTRYPOINT
  • SQL Server does not have the ability to execute a script on startup.
  • Mounting volumes may not work as expected on macOS, but stackoverflow has a solution.
  • Be careful when building using docker compose from any directory outside of your repo. It adds files from the specified context, which is often your current working directory, so, for instance, don't execute from your $home directory. You can read more here.
  • VOLUMEs are tricky and if you don't do them right, you can end up leaving a lot of garbage for your end-users.

image

Some of the best resources I found included:

Repos

Tips and Tricks

Push different architecture builds under the same tag

Here's how I made dbatools/sqlinstance:latest available for both amd64 (x64) and arm64.

# create arm64 image on an Apple M1 and push it out to docker hub
docker push dbatools/sqlinstance:latest-arm64
# do the same on my Windows machine but use the amd64 tag
docker push dbatools/sqlinstance:latest-amd64

# Remove manifest cache just in case
Remove-Item $HOME/.docker/manifests -Recurse -Force

# Create manifests that support  multiple architectures
docker manifest create dbatools/sqlinstance:latest --amend dbatools/sqlinstance:latest-amd64 --amend dbatools/sqlinstance:latest-arm64

# view it if you want
docker manifest inspect docker.io/dbatools/sqlinstance:latest

# push out to docker and purge the old image
docker manifest push docker.io/dbatools/sqlinstance:latest --purge

Directly copy files and directories from any image

You can easily grab specific files from any image.

Here's how I compiled sqlcmd on go using the files in /usr/local/go on go's main image.

COPY --from=golang /usr/local/go/ /usr/local/go/
ENV PATH="/usr/local/go/bin:${PATH}"

wget https://github.com/microsoft/go-sqlcmd/archive/refs/heads/main.zip
unzip main.zip
pushd /tmp/go-sqlcmd-main
go build -o /tmp/sqlcmd ./cmd/sqlcmd
popd
/tmp/sqlcmd

Here's how I got pwsh for ARM64. I got it from the dotnet sdk container, as there's no powershell tag yet for arm64

COPY --from=mcr.microsoft.com/dotnet/sdk:3.1-bionic-arm64v8 /usr/share/powershell /bin

TO DO

Use emulation to build ARM-based containers on GitHub