Skip to content
Open
Show file tree
Hide file tree
Changes from 13 commits
Commits
Show all changes
24 commits
Select commit Hold shift + click to select a range
b6ce198
feat(templates): add docker-texlive template with code-server
joergklein Apr 2, 2026
3ff24f1
refactor: move template to registry/joergklein/templates/docker-texlive
blink-so[bot] Apr 2, 2026
d398760
Update README.md
joergklein Apr 3, 2026
d0de2f7
Merge branch 'coder:main' into feat/docker-texlive-template
joergklein Apr 3, 2026
d44aec5
Add an avatar
joergklein Apr 3, 2026
c416ee4
chore: replace texlive.svg with PNG icon from TeX Live GitHub avatar
blink-so[bot] Apr 3, 2026
bae66fb
Apply suggestion from @matifali
DevelopmentCats Apr 3, 2026
5c2ea35
Merge branch 'coder:main' into feat/docker-texlive-template
joergklein Apr 4, 2026
1ddea10
- Multiple workspaces on the same host
joergklein Apr 4, 2026
6b00883
Update main.tf
joergklein Apr 8, 2026
1c0048a
Update README.md
joergklein Apr 8, 2026
8ae183d
Merge branch 'coder:main' into feat/docker-texlive-template
joergklein Apr 8, 2026
26c8168
Update Dockerfile
joergklein Apr 8, 2026
b38d2d9
format the files
joergklein Apr 9, 2026
0b5df32
Merge branch 'coder:main' into feat/docker-texlive-template
joergklein Apr 9, 2026
ea3c48a
Extensions, the "settings.json" file, and the TeX Live projects are n…
joergklein Apr 13, 2026
dd646d7
Merge branch 'coder:main' into feat/docker-texlive-template
joergklein Apr 13, 2026
6a87711
Update Dockerfile
joergklein Apr 13, 2026
16db072
Update main.tf
joergklein Apr 13, 2026
ade2024
Update Dockerfile
joergklein Apr 13, 2026
4555692
fix: formatting, avatar path, image name, and remove timestamp from i…
DevelopmentCats Apr 13, 2026
2658d3d
fix: restore section comments removed in previous commit
DevelopmentCats Apr 13, 2026
16e2169
fix: move texlive icon to .icons/ and update README to reference it
DevelopmentCats Apr 13, 2026
0bd2ca6
fix: replace texlive.png with SVG icon and update README reference
DevelopmentCats Apr 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added registry/joergklein/.images/avatar.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
11 changes: 11 additions & 0 deletions registry/joergklein/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
display_name: "Jörg Klein"
bio: "Data Scientists"
github: "joergklein"
avatar: "./.images/avatar.jpeg"
status: "community"
---

# Jörg Klein

Data Scientists
90 changes: 90 additions & 0 deletions registry/joergklein/templates/docker-texlive/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
---
display_name: Docker TeX Live
description: Provision Docker containers with TeX Live, code-server
icon: ../../../../.icons/docker.svg
tags: [docker, texlive]
---

# TeX Live Development on Docker Containers

Provision Docker containers pre-configured for TeX development as [Coder workspaces](https://coder.com/docs/workspaces) with this template.

Each workspace comes with:

- **TeX Live** — TeX Live is a comprehensive, cross-platform distribution for TeX and LaTeX systems that provides all necessary programs, macro packages, and fonts for professional typesetting.
- **code-server** — VS Code in the browser for general editing.

The workspace is based on the [TeX Live](https://www.tug.org/texlive) image. It provides nearly all packages from the [Comprehensive TeX Archive Network (CTAN)](https://www.ctan.org), although some non-free packages may be restricted.

## Prerequisites

### Infrastructure

#### Running Coder inside Docker

If you installed Coder as a container within Docker, you will have to do the following things:

- Make the Docker socket available to the container
- **(recommended) Mount `/var/run/docker.sock` via `--mount`/`volume`**
- _(advanced) Restrict the Docker socket via https://github.com/Tecnativa/docker-socket-proxy_
- Set `--group-add`/`group_add` to the GID of the Docker group on the **host** machine
- You can get the GID by running `getent group docker` on the **host** machine

#### Running Coder outside of Docker

If you installed Coder as a system package, the VM you run Coder on must have a running Docker socket and the `coder` user must be added to the Docker group:

```bash
# Add coder user to Docker group
sudo adduser coder docker

# Restart Coder server
sudo systemctl restart coder

# Test Docker
sudo -u coder docker ps
```

## Architecture

This template provisions the following resources:

- Docker image (built from `build/Dockerfile`, extending `registry.gitlab.com/islandoftex/images/texlive` with system dependencies)
- Docker container (ephemeral — destroyed on workspace stop)
- Docker volume (persistent on `/home/texlive`)

When the workspace restarts, tools and files outside `/home/texlive` are not persisted.

> [!NOTE]
> This template is designed to be a starting point! Edit the Terraform to extend it for your use case.

## Customization

The continuous integration is scheduled to rebuild all Docker images weekly. Hence, pulling the latest image will provide you with an at most one week old snapshot of TeX Live including all packages. You can manually update within the container by running `tlmgr update --self --all`.

Each of the weekly builds is tagged with `TL{RELEASE}-{YEAR}-{MONTH}-{DAY}-{HOUR}-{MINUTE}` apart from being latest for one week. If you want to have reproducible builds or happen to find a regression in a later image you can still revert to a date that worked, e.g. `TL2019-2019-08-01-08-14 or latest`.

- [Container Registry TeX Live](https://gitlab.com/islandoftex/images/texlive/container_registry)
- [Dockerhub TeX Live](https://hub.docker.com/r/texlive/texlive)

### Installing additional TeX packages

If you want to update packages from CTAN after installation, see these [examples of using tlmgr](https://tug.org/texlive/doc/tlmgr.html#EXAMPLES). This is not required, or even necessarily recommended; it's up to you to decide if it makes sense to get continuing updates in your particular situation.

Typically the main binaries are not updated in TeX Live between major releases. If you want to get updates for LuaTeX and other packages and programs that aren't officially released yet, they may be available in the [TLContrib repository](http://contrib.texlive.info), or you may need to [compile the sources](https://tug.org/texlive/svn) yourself.

### Adding system dependencies

The `build/Dockerfile` extends the `registry.gitlab.com/islandoftex/images/texlive` base image with system packages required by modules (e.g. `curl` for code-server). If you add modules that need additional system-level tools, add them to the `Dockerfile`:

```dockerfile
RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
inkscape \
unzip \
vim \
wget \
your-package-here \
&& rm -rf /var/lib/apt/lists/*
```
25 changes: 25 additions & 0 deletions registry/joergklein/templates/docker-texlive/build/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
ARG TEXLIVE_VERSION=latest
FROM registry.gitlab.com/islandoftex/images/texlive:${TEXLIVE_VERSION}

RUN apt-get update \
&& apt-get install -y --no-install-recommends \
curl \
inkscape \
unzip \
vim \
wget \
&& rm -rf /var/lib/apt/lists/*

# Set locale to UTF-8
ENV LANG=C.UTF-8
ENV LC_ALL=C.UTF-8

# Optional: Update TeX Live packages
# Only enable if you need the latest packages
# RUN tlmgr update --self --all

# Working directory inside the container
WORKDIR /home/texlive

# Icon
COPY icon/ /icon/
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
208 changes: 208 additions & 0 deletions registry/joergklein/templates/docker-texlive/main.tf
Original file line number Diff line number Diff line change
@@ -0,0 +1,208 @@
terraform {
required_providers {
coder = {
source = "coder/coder"
}
docker = {
source = "kreuzwerker/docker"
}
}
}

variable "docker_socket" {
default = ""
description = "(Optional) Docker socket URI"
type = string
}

variable "texlive_version" {
default = "latest"
description = "The TeX Live image tag to use (e.g., TL2025-2025-01-01-08-14 or latest)"
type = string
}

provider "docker" {
host = var.docker_socket != "" ? var.docker_socket : null
}

data "coder_provisioner" "me" {}
data "coder_workspace" "me" {}
data "coder_workspace_owner" "me" {}

locals {
username = try(data.coder_workspace_owner.me.name, "unknown_user")
start_count = try(data.coder_workspace.me.start_count, 0)

build_context_hash = sha1(join("", [
for f in fileset("${path.module}/build", "**") :
try(filesha1("${path.module}/build/${f}"), "")
]))

date_tag = formatdate("YYYY-MM-DD-HH-mm", timestamp())

weekly_trigger = try(
formatdate("YYYY", timestamp()) + "-W" + tostring(ceil(tonumber(formatdate("DDD", timestamp())) / 7)),
"2026-W01"
)
}

resource "coder_agent" "main" {
arch = try(data.coder_provisioner.me.arch, "x86_64")
os = "linux"

startup_script = <<-EOT
set -e
if [ ! -f ~/.init_done ]; then
cp -rT /etc/skel ~ 2>/dev/null || true
touch ~/.init_done
fi
EOT

env = {
GIT_AUTHOR_NAME = coalesce(try(data.coder_workspace_owner.me.full_name, ""), local.username)
GIT_AUTHOR_EMAIL = try(data.coder_workspace_owner.me.email, "unknown@example.com")
GIT_COMMITTER_NAME = coalesce(try(data.coder_workspace_owner.me.full_name, ""), local.username)
GIT_COMMITTER_EMAIL = try(data.coder_workspace_owner.me.email, "unknown@example.com")
}

metadata {
display_name = "CPU Usage"
key = "0_cpu_usage"
script = "coder stat cpu"
interval = 10
timeout = 1
}

metadata {
display_name = "RAM Usage"
key = "1_ram_usage"
script = "coder stat mem"
interval = 10
timeout = 1
}

metadata {
display_name = "Home Disk"
key = "3_home_disk"
script = "coder stat disk --path $${HOME}"
interval = 60
timeout = 1
}
}

module "code-server" {
count = local.start_count
source = "registry.coder.com/coder/code-server/coder"

version = "~> 1.0"
agent_id = coder_agent.main.id
order = 1
folder = "/home/texlive"
}

resource "docker_image" "texlive" {
name = "registry.example.com/texlive:TL${var.texlive_version}-${try(data.coder_workspace.me.id, 0)}-${substr(local.build_context_hash, 0, 8)}-${local.date_tag}"

build {
context = "${path.module}/build"
dockerfile = "Dockerfile"

build_args = {
TEXLIVE_VERSION = var.texlive_version
}
}

triggers = {
dir_hash = local.build_context_hash
texlive_version = var.texlive_version
latest_rebuild = local.weekly_trigger
}
}

resource "docker_volume" "home_volume" {
name = "coder-${try(data.coder_workspace.me.id, 0)}-home"

lifecycle {
ignore_changes = all
}

labels {
label = "coder.owner"
value = local.username
}
labels {
label = "coder.owner_id"
value = try(data.coder_workspace_owner.me.id, "0")
}
labels {
label = "coder.workspace_id"
value = try(data.coder_workspace.me.id, "0")
}
labels {
label = "coder.workspace_name_at_creation"
value = try(data.coder_workspace.me.name, "unknown_workspace")
}
}

resource "docker_container" "workspace" {
count = local.start_count
image = docker_image.texlive.image_id
name = "coder-${local.username}-${lower(try(data.coder_workspace.me.name, "workspace"))}"
hostname = try(data.coder_workspace.me.name, "workspace")

entrypoint = [
"sh",
"-c",
replace(coder_agent.main.init_script, "/localhost|127\\.0\\.0\\.1/", "host.docker.internal")
]

env = ["CODER_AGENT_TOKEN=${coder_agent.main.token}"]

host {
host = "host.docker.internal"
ip = "host-gateway"
}

volumes {
container_path = "/home/texlive"
volume_name = docker_volume.home_volume.name
read_only = false
}

labels {
label = "coder.owner"
value = local.username
}
labels {
label = "coder.owner_id"
value = try(data.coder_workspace_owner.me.id, "0")
}
labels {
label = "coder.workspace_id"
value = try(data.coder_workspace.me.id, "0")
}
labels {
label = "coder.workspace_name"
value = try(data.coder_workspace.me.name, "workspace")
}
}

resource "null_resource" "cleanup_old_texlive_images" {
triggers = {
current_image = docker_image.texlive.name
}

provisioner "local-exec" {
command = <<EOT
CURRENT_ID=$(docker inspect --format='{{.Id}}' ${docker_image.texlive.name})

docker images --format "{{.Repository}}:{{.Tag}} {{.ID}}" \
| grep "registry.example.com/texlive:${var.texlive_version}-${try(data.coder_workspace.me.id, 0)}" \
| grep -v "$CURRENT_ID" \
| awk '{print $2}' \
| xargs -r docker rmi -f
EOT
}

depends_on = [docker_image.texlive]
}
Loading