Ops are configured using two files: ops.yml
and Dockerfile
. This document will guide you through making best use of these files to make a well-behaved, well-optimized Op experience for your users. Please consult the reference documentation at https://cto.ai/docs/configuring-ops alongside this document.
Only use ops.yml features you need
The ops.yml
definition contains a number of features for local Ops that can cause problems if they are not needed. For maximum compatibility with all of our interfaces, we recommend avoiding the bind
and port
sections, and setting both mountCwd
and mountHome
to false
. None of these features function when running Ops from Slack, and there may be conflicts on the local machine if they are set but not used.
Avoid leaving unneeded files in the image
Due to the layered structure of Docker images, any files in the final image that persist between RUN
commands are included in the download of the eventual Op. With the Debian package system used to install additional software, care must be taken to remove its cached files so that they do not bloat the image. To install packages, we recommend the following RUN
entry:
RUN apt-get update \
&& apt-get install -y <your package names> \
&& rm -rf /var/lib/apt/lists
The last line removes the cached package lists, which keeps them out of the image entirely.
Similarly, it might be necessary to install a package to install a needed tool, e.g. pip
to install awscli
. In this case we can install and use the tool in one RUN
entry, and then remove it with apt-get purge -y
.
Try installing packages with --no-install-recommends
In the Debian system, packages can specify different kinds of relationships with other packages. By default, apt-get
installs both dependencies (which are mandatory), and recommends (which are not). Recommends generally assume that the package is being installed onto a general-purpose system, which is not what we need for a Docker container.
The --no-install-recommends
flag to apt-get
switches to only installing mandatory dependencies. This is usually what we want, but there may be certain recommends that you need, so it may take some experimentation.
One common program with large recommends is pip
for managing Python packages. It recommends a full C toolchain since Python packages may come with C extensions to build. Generally, this is unneeded, so we will have a much smaller image (around 300MB smaller) if instead we do:
apt-get install -y --no-install-recommends python-pip python-setuptools python-wheel
Note that if you are writing an op in Python the Python 3.7 version of pip
is installed in the official base image and you do not need to install it manually.
Build in a separate image
One important feature of Dockerfiles that we use in The Ops Platform is staged builds. This allows for space-intensive work like building code to occur in a different Docker context from the final image that is published. This is especially important for compiled languages like Go, which often have large toolchains needed to build the Op code but not to run it.
A simple example of a multi-stage Dockerfile is:
############################
# Build container
############################
FROM golang:1.14-stretch AS dep
WORKDIR /ops
ADD . .
RUN go build -ldflags="-s -w" -o main && strip -s main && chmod 777 main
############################
# Final container
############################
FROM registry.cto.ai/official_images/base:2-stretch-slim
WORKDIR /ops
COPY --from=dep /ops/main .
This builds the Go code inside a container with all of the Go tools available, then copies it into an otherwise unaltered CTO.ai base image. For the end user, then, the necessary download is at most the base image plus the size of the compiled Go code, and they need not download or store the Go compiler to use the Op.