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.