example: add docker-image-builds + docker docs (#1526)

Co-authored-by: Katie Horne <katie@23spoons.com>
This commit is contained in:
Ben Potter
2022-05-18 16:03:20 -05:00
committed by GitHub
co-authored by Katie Horne
parent 97699e9704
commit 37cf3bb491
9 changed files with 557 additions and 66 deletions
+159
View File
@@ -0,0 +1,159 @@
---
name: Develop in Docker with custom image builds
description: Build images and run workspaces on the Docker host with no image registry required
tags: [local, docker]
---
# docker-image-builds
This example bundles Dockerfiles with the Coder template, allowing the Docker host to build images itself instead of relying on an external registry.
For large use cases, we recommend building images using CI/CD pipelines and registries instead of at workspace runtime. However, this example is practical for tinkering and iterating on Dockerfiles.
## Getting started
Run `coder templates init` and select this template. Follow the instructions that appear.
## Adding images
Create a Dockerfile (e.g `images/golang.Dockerfile`):
```sh
vim images/golang.Dockerfile
```
```Dockerfile
# Start from base image (built on Docker host)
FROM coder-base:latest
# Install everything as root
USER root
# Install go
RUN curl -L "https://dl.google.com/go/go1.18.1.linux-amd64.tar.gz" | tar -C /usr/local -xzvf -
# Setup go env vars
ENV GOROOT /usr/local/go
ENV PATH $PATH:$GOROOT/bin
ENV GOPATH /home/coder/go
ENV GOBIN $GOPATH/bin
ENV PATH $PATH:$GOBIN
# Set back to coder user
USER coder
```
Edit the Terraform template (`main.tf`):
```sh
vim main.tf
```
Edit the validation to include the new image:
```diff
variable "docker_image" {
description = "What Docker imagewould you like to use for your workspace?"
default = "base"
# List of images available for the user to choose from.
# Delete this condition to give users free text input.
validation {
- condition = contains(["base", "java", "node"], var.docker_image)
+ condition = contains(["base", "java", "node", "golang], var.docker_image)
error_message = "Invalid Docker image!"
}
}
```
Bump the image tag to a new version:
```diff
resource "docker_image" "coder_image" {
name = "coder-base-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}"
build {
path = "./images/"
dockerfile = "${var.docker_image}.Dockerfile"
- tag = ["coder-${var.docker_image}:v0.1"]
+ tag = ["coder-${var.docker_image}:v0.2"]
}
# Keep alive for other workspaces to use upon deletion
keep_locally = true
}
```
Update the template:
```sh
coder template update docker-image-builds
```
You can also remove images from the validation list. Workspaces using older template versions will continue using
the removed image until you update the workspace to the latest version.
## Updating images
Edit the Dockerfile (or related assets):
```sh
vim images/node.Dockerfile
```
```diff
# Install Node
- RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
+ RUN curl -sL https://deb.nodesource.com/setup_16.x | bash -
RUN DEBIAN_FRONTEND="noninteractive" apt-get update -y && \
apt-get install -y nodejs
```
1. Edit the Terraform template (`main.tf`)
```sh
vim main.tf
```
Bump the image tag to a new version:
```diff
resource "docker_image" "coder_image" {
name = "coder-base-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}"
build {
path = "./images/"
dockerfile = "${var.docker_image}.Dockerfile"
- tag = ["coder-${var.docker_image}:v0.1"]
+ tag = ["coder-${var.docker_image}:v0.2"]
}
# Keep alive for other workspaces to use upon deletion
keep_locally = true
}
```
Update the template:
```sh
coder template update docker-image-builds
```
Optional: Update workspaces to the latest template version
```sh
coder ls
coder update [workspace name]
```
## Extending this template
See the [kreuzwerker/docker](https://registry.terraform.io/providers/kreuzwerker/docker) Terraform provider documentation to
add the following features to your Coder template:
- SSH/TCP docker host
- Build args
- Volume mounts
- Custom container spec
- More
We also welcome all contributions!
@@ -0,0 +1,33 @@
FROM ubuntu:20.04
RUN apt-get update && \
DEBIAN_FRONTEND="noninteractive" apt-get install --yes \
bash \
build-essential \
ca-certificates \
curl \
htop \
locales \
man \
python3 \
python3-pip \
software-properties-common \
sudo \
systemd \
systemd-sysv \
unzip \
vim \
wget && \
# Install latest Git using their official PPA
add-apt-repository ppa:git-core/ppa && \
DEBIAN_FRONTEND="noninteractive" apt-get install --yes git
# Add a user `coder` so that you're not developing as the `root` user
RUN useradd coder \
--create-home \
--shell=/bin/bash \
--uid=1000 \
--user-group && \
echo "coder ALL=(ALL) NOPASSWD:ALL" >>/etc/sudoers.d/nopasswd
USER coder
@@ -0,0 +1,57 @@
# From the base image (built on Docker host)
FROM coder-base:v0.1
# Install everything as root
USER root
# Install JDK (OpenJDK 8)
RUN DEBIAN_FRONTEND="noninteractive" apt-get update -y && \
apt-get install -y openjdk-11-jdk
ENV JAVA_HOME /usr/lib/jvm/java-11-openjdk-amd64
ENV PATH $PATH:$JAVA_HOME/bin
# Install Maven
ARG MAVEN_VERSION=3.6.3
ARG MAVEN_SHA512=c35a1803a6e70a126e80b2b3ae33eed961f83ed74d18fcd16909b2d44d7dada3203f1ffe726c17ef8dcca2dcaa9fca676987befeadc9b9f759967a8cb77181c0
ENV MAVEN_HOME /usr/share/maven
ENV MAVEN_CONFIG "/home/coder/.m2"
RUN mkdir -p $MAVEN_HOME $MAVEN_HOME/ref \
&& echo "Downloading Maven" \
&& curl -fsSL -o /tmp/apache-maven.tar.gz https://apache.osuosl.org/maven/maven-3/${MAVEN_VERSION}/binaries/apache-maven-${MAVEN_VERSION}-bin.tar.gz \
\
&& echo "Checking downloaded file hash" \
&& echo "${MAVEN_SHA512} /tmp/apache-maven.tar.gz" | sha512sum -c - \
\
&& echo "Unzipping Maven" \
&& tar -xzf /tmp/apache-maven.tar.gz -C $MAVEN_HOME --strip-components=1 \
\
&& echo "Cleaning and setting links" \
&& rm -f /tmp/apache-maven.tar.gz \
&& ln -s $MAVEN_HOME/bin/mvn /usr/bin/mvn
# Install Gradle
ENV GRADLE_VERSION=6.7
ARG GRADLE_SHA512=d495bc65379d2a854d2cca843bd2eeb94f381e5a7dcae89e6ceb6ef4c5835524932313e7f30d7a875d5330add37a5fe23447dc3b55b4d95dffffa870c0b24493
ENV GRADLE_HOME /usr/bin/gradle
RUN mkdir -p /usr/share/gradle /usr/share/gradle/ref \
&& echo "Downloading Gradle" \
&& curl -fsSL -o /tmp/gradle.zip https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip \
\
&& echo "Checking downloaded file hash" \
&& echo "${GRADLE_SHA512} /tmp/gradle.zip" | sha512sum -c - \
\
&& echo "Unziping gradle" \
&& unzip -d /usr/share/gradle /tmp/gradle.zip \
\
&& echo "Cleaning and setting links" \
&& rm -f /tmp/gradle.zip \
&& ln -s /usr/share/gradle/gradle-${GRADLE_VERSION} /usr/bin/gradle
ENV PATH $PATH:$GRADLE_HOME/bin
# Set back to coder user
USER coder
@@ -0,0 +1,18 @@
# Start from base image (built on Docker host)
FROM coder-base:v0.1
# Install everything as root
USER root
# Install Node
RUN curl -sL https://deb.nodesource.com/setup_14.x | bash -
RUN DEBIAN_FRONTEND="noninteractive" apt-get update -y && \
apt-get install -y nodejs
# Install Yarn
RUN curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add -
RUN echo "deb https://dl.yarnpkg.com/debian/ stable main" | tee /etc/apt/sources.list.d/yarn.list
RUN DEBIAN_FRONTEND="noninteractive" apt-get update && apt-get install -y yarn
# Set back to coder user
USER coder
+111
View File
@@ -0,0 +1,111 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
version = "0.4.1"
}
docker = {
source = "kreuzwerker/docker"
version = "~> 2.16.0"
}
}
}
# Admin parameters
variable "step1_docker_host_warning" {
description = <<-EOF
This template will use the Docker socket present on
the Coder host, which is not necessarily your local machine.
You can specify a different host in the template file and
surpress this warning.
EOF
validation {
condition = contains(["Continue using /var/run/docker.sock on the Coder host"], var.step1_docker_host_warning)
error_message = "Cancelling template create."
}
sensitive = true
}
variable "step2_arch" {
description = "arch: What archicture is your Docker host on?"
validation {
condition = contains(["amd64", "arm64", "armv7"], var.step2_arch)
error_message = "Value must be amd64, arm64, or armv7."
}
sensitive = true
}
provider "docker" {
host = "unix:///var/run/docker.sock"
}
provider "coder" {
# The below assumes your Coder deployment is running in docker-compose.
# If this is not the case, either comment or edit the below.
url = "http://host.docker.internal:7080"
}
data "coder_workspace" "me" {
}
resource "coder_agent" "dev" {
arch = var.step2_arch
os = "linux"
}
variable "docker_image" {
description = "What Docker imagewould you like to use for your workspace?"
default = "base"
# List of images available for the user to choose from.
# Delete this condition to give users free text input.
validation {
condition = contains(["base", "java", "node"], var.docker_image)
error_message = "Invalid Docker image!"
}
# Prevents admin errors when the image is not found
validation {
condition = fileexists("images/${var.docker_image}.Dockerfile")
error_message = "Invalid Docker image. The file does not exist in the images directory."
}
}
resource "docker_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}-root"
}
resource "docker_image" "coder_image" {
name = "coder-base-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}"
build {
path = "./images/"
dockerfile = "${var.docker_image}.Dockerfile"
tag = ["coder-${var.docker_image}:v0.1"]
}
# Keep alive for other workspaces to use upon deletion
keep_locally = true
}
resource "docker_container" "workspace" {
count = data.coder_workspace.me.start_count
image = docker_image.coder_image.latest
# Uses lower() to avoid Docker restriction on container names.
name = "coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}"
# Hostname makes the shell more user friendly: coder@my-workspace:~$
hostname = lower(data.coder_workspace.me.name)
dns = ["1.1.1.1"]
command = ["sh", "-c", coder_agent.dev.init_script]
env = ["CODER_AGENT_TOKEN=${coder_agent.dev.token}"]
host {
host = "host.docker.internal"
ip = "host-gateway"
}
volumes {
container_path = "/home/coder/"
volume_name = docker_volume.home_volume.name
read_only = false
}
}
-5
View File
@@ -1,5 +0,0 @@
---
name: Develop in Docker on the same host that runs Coder
description: Get started with Linux development using a Docker container locally as workspace provider.
tags: [local, docker]
---
-61
View File
@@ -1,61 +0,0 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
version = "0.3.4"
}
docker = {
source = "kreuzwerker/docker"
version = "~> 2.16.0"
}
}
}
provider "docker" {
host = "unix:///var/run/docker.sock"
}
provider "coder" {
# The below assumes your Coder deployment is running in docker-compose.
# If this is not the case, either comment or edit the below.
url = "http://host.docker.internal:7080"
}
data "coder_workspace" "me" {
}
resource "coder_agent" "dev" {
arch = "amd64"
os = "linux"
}
variable "docker_image" {
description = "What docker image would you like to use for your workspace?"
default = "codercom/enterprise-base:ubuntu"
validation {
condition = contains(["codercom/enterprise-base:ubuntu", "codercom/enterprise-node:ubuntu", "codercom/enterprise-intellij:ubuntu"], var.docker_image)
error_message = "Invalid Docker Image!"
}
}
resource "docker_volume" "coder_volume" {
name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}-root"
}
resource "docker_container" "workspace" {
count = data.coder_workspace.me.start_count
image = var.docker_image
name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}-root"
dns = ["1.1.1.1"]
command = ["sh", "-c", coder_agent.dev.init_script]
env = ["CODER_AGENT_TOKEN=${coder_agent.dev.token}"]
host {
host = "host.docker.internal"
ip = "host-gateway"
}
volumes {
container_path = "/home/coder/"
volume_name = docker_volume.coder_volume.name
read_only = false
}
}
+80
View File
@@ -0,0 +1,80 @@
---
name: Develop in Docker
description: Run workspaces on a Docker host using registry images
tags: [local, docker]
---
# docker
## Getting started
Run `coder templates init` and select this template. Follow the instructions that appear.
## Adding/removing images
After building and pushing an image to an image registry (e.g., DockerHub), you can edit the template to make the image available to users.
Edit the template:
```sh
vim main.tf
```
variable "docker_image" {
description = "What Docker imagewould you like to use for your workspace?"
default = "codercom/enterprise-base:ubuntu"
validation {
- condition = contains(["codercom/enterprise-base:ubuntu", "codercom/enterprise-node:ubuntu", "codercom/enterprise-intellij:ubuntu"], var.docker_image)
+ condition = contains(["codercom/enterprise-base:ubuntu", "codercom/enterprise-node:ubuntu", "codercom/enterprise-intellij:ubuntu", "codercom/enterprise-golang:ubuntu"], var.docker_image)
error_message = "Invalid Docker image!"
}
}
```
Update the template:
```sh
coder template update docker
```
You can also remove images from the validation list. Workspaces using older template versions will continue using
the removed image until you update the workspace to the latest version.
## Updating images
To reduce drift, we recommend versioning images in your registry by creating tags. To update the image tag in the template:
```sh
variable "docker_image" {
description = "What Docker imagewould you like to use for your workspace?"
default = "codercom/enterprise-base:ubuntu"
validation {
- condition = contains(["my-org/base-development:v1.1", "myorg-java-development:v1.1"], var.docker_image)
+ condition = contains(["my-org/base-development:v1.1", "myorg-java-development:v1.2"], var.docker_image)
error_message = "Invalid Docker image!"
}
}
```
Optional: Update workspaces to the latest template version:
```sh
coder ls
coder update [workspace name]
```
## Extending this template
See the [kreuzwerker/docker](https://registry.terraform.io/providers/kreuzwerker/docker) Terraform provider documentation to
add the following features to your Coder template:
- SSH/TCP docker host
- Registry authentication
- Build args
- Volume mounts
- Custom container spec
- More
We also welcome contributions!
+99
View File
@@ -0,0 +1,99 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
version = "0.4.1"
}
docker = {
source = "kreuzwerker/docker"
version = "~> 2.16.0"
}
}
}
# Admin parameters
# Comment this out if you are specifying a different docker
# host on the "docker" provider below.
variable "step1_docker_host_warning" {
description = <<-EOF
This template will use the Docker socket present on
the Coder host, which is not necessarily your local machine.
You can specify a different host in the template file and
surpress this warning.
EOF
validation {
condition = contains(["Continue using /var/run/docker.sock on the Coder host"], var.step1_docker_host_warning)
error_message = "Cancelling template create."
}
sensitive = true
}
variable "step2_arch" {
description = <<-EOF
arch: What archicture is your Docker host on?
note: codercom/enterprise-* images are only built for amd64
EOF
validation {
condition = contains(["amd64", "arm64", "armv7"], var.step2_arch)
error_message = "Value must be amd64, arm64, or armv7."
}
sensitive = true
}
provider "docker" {
host = "unix:///var/run/docker.sock"
}
provider "coder" {
# The below assumes your Coder deployment is running in docker-compose.
# If this is not the case, either comment or edit the below.
url = "http://host.docker.internal:7080"
}
data "coder_workspace" "me" {
}
resource "coder_agent" "dev" {
arch = var.step2_arch
os = "linux"
}
variable "docker_image" {
description = "Which Docker image would you like to use for your workspace?"
# The codercom/enterprise-* images are only built for amd64
default = "codercom/enterprise-base:ubuntu"
validation {
condition = contains(["codercom/enterprise-base:ubuntu", "codercom/enterprise-node:ubuntu", "codercom/enterprise-intellij:ubuntu"], var.docker_image)
error_message = "Invalid Docker image!"
}
}
resource "docker_volume" "home_volume" {
name = "coder-${data.coder_workspace.me.owner}-${data.coder_workspace.me.name}-root"
}
resource "docker_container" "workspace" {
count = data.coder_workspace.me.start_count
image = var.docker_image
# Uses lower() to avoid Docker restriction on container names.
name = "coder-${data.coder_workspace.me.owner}-${lower(data.coder_workspace.me.name)}"
# Hostname makes the shell more user friendly: coder@my-workspace:~$
hostname = lower(data.coder_workspace.me.name)
dns = ["1.1.1.1"]
command = ["sh", "-c", coder_agent.dev.init_script]
env = ["CODER_AGENT_TOKEN=${coder_agent.dev.token}"]
host {
host = "host.docker.internal"
ip = "host-gateway"
}
volumes {
container_path = "/home/coder/"
volume_name = docker_volume.home_volume.name
read_only = false
}
}