Self-hosted Coder instance for jardis-based development workspaces, running on Docker Compose.
docker-compose.yml # Coder server + PostgreSQL
.env # Secrets (gitignored — see .env.template)
push-template.sh # Helper to push any template (dereferences symlinks)
new-template.sh # Scaffold a new template directory
modules/
jardis-workspace/ # Shared Terraform module — all workspace logic lives here
main.tf
variables.tf
build/
Dockerfile
<template-name>/ # One directory per workspace template
main.tf # Thin caller: providers + module block with template values
modules -> ../modules # Symlink so terraform init resolves the module locally
All workspace logic (startup script, Docker image, code-server setup) lives in modules/jardis-workspace/ and is reused by every template. Only the five values in the module "workspace" block differ between templates.
Create the shared workspace directory on the host. Each user's workspace will bind-mount its own subdirectory from here. The base path is set via users_workspace_path in the module "workspace" block of the template's main.tf (default: /home/kokos/users-workspace).
sudo mkdir -p /home/kokos/users-workspace
sudo chmod 777 /home/kokos/users-workspacemacOS / Docker Desktop only: Docker Desktop restricts which host paths can be bind-mounted into containers. Add the base directory to the allowed list before starting workspaces: Docker Desktop → Settings → Resources → File Sharing → add the path → Apply & Restart
On Linux this restriction does not exist.
cp .env.template .envFill in .env with the GitHub OAuth App credentials (see section below).
You can also set the following optional defaults that new-template.sh will read when scaffolding new templates:
| Variable | Purpose |
|---|---|
DEFAULT_JARDIS_HOST |
Pre-fills the jardis_host prompt |
DEFAULT_JARDIS_PORT |
Pre-fills the jardis_port prompt |
DEFAULT_USERS_WORKSPACE_PATH |
Pre-fills the users_workspace_path prompt |
docker compose up -dCoder is available at http://localhost:7080.
Open http://localhost:7080 and complete the initial setup wizard.
| Path | Visible to | Persists across workspace destroy? |
|---|---|---|
~/ (home volume) |
workspace only | No |
~/<workspace-name>/libs |
workspace + host | Yes |
When a workspace starts, repos are cloned into $USERS_WORKSPACE_PATH/<username>/libs/ on the host filesystem. Inside the container, only $USERS_WORKSPACE_PATH/<username>/libs is bind-mounted — other users' directories are never visible from within the workspace. ~/<workspace-name>/libs is a symlink to that host path, where <workspace-name> is the name the user chose when creating the Coder workspace.
- From the host: files are at
$USERS_WORKSPACE_PATH/<username>/libs/ - From an external container: mount
$USERS_WORKSPACE_PATHto access all users' repos, each under their own subdirectory - Survives workspace destruction: the files live on the host, not in the home Docker volume. Recreating the workspace skips cloning since the repos are already there
Replace $USERS_WORKSPACE_PATH with the value of users_workspace_path in the template's main.tf (default /home/kokos/users-workspace):
services:
myapp:
image: myapp:latest
volumes:
- /home/kokos/users-workspace:/workspace-libs:roEach user's repos are then at /workspace-libs/<coder-username>/<repo-name> etc.
Everything else (shell history, editor config, uncommitted files outside libs) lives in a Docker named volume private to that workspace. It is not accessible from the host or other containers, and is deleted when the workspace is destroyed.
The Jardis host, port, and environment are injected into the code-server user settings (~/.local/share/code-server/User/settings.json) on every workspace start, so they always reflect the current template values.
Edit the values in the module "workspace" block of <template-name>/main.tf:
module "workspace" {
source = "./modules/jardis-workspace"
jardis_host = "localhost" # ← change this
jardis_port = 9091 # ← change this
jardis_env = "my-env-user" # ← change this
...
}Then push the template:
./push-template.sh <template-name>On the next workspace start, the startup script merges these values into the user settings file:
{
"jardis.user": "<coder-username>",
"jardis.host": "<jardis_host>",
"jardis.port": <jardis_port>,
"jardis.env": "<jardis_env>"
}Because settings are re-applied on every start, pushing the template and stopping/starting the workspace is enough to pick up new values.
Two separate OAuth Apps are required because each uses a different callback URL.
| App name | Callback URL | Purpose |
|---|---|---|
coder-login |
http://<host>:7080/api/v2/users/oauth2/github/callback |
"Sign in with GitHub" on the Coder login page |
coder-external |
http://<host>:7080/external-auth/github/callback |
Workspace template access to private repos and release assets |
Create each at GitHub → Settings → Developer settings → OAuth Apps → New OAuth App.
The smeup org restricts third-party OAuth App access. Each app must be approved by an org admin before it can access private org resources.
Approval flow (repeat for each app):
- Sign in to Coder with the app (login app) or create a workspace (external app) — this triggers the org access block
- Go to GitHub → Settings → Applications → Authorized OAuth Apps, click the app, click Request access next to
smeup - As org admin, go to
https://github.com/organizations/smeup/settings/oauth_application_policyand approve it
./new-template.sh <template-name>
<template-name>should match the kokos application name (e.g.smeuperp,demo).
The script prompts for the required values and scaffolds a new template directory. If DEFAULT_JARDIS_HOST, DEFAULT_JARDIS_PORT, or DEFAULT_USERS_WORKSPACE_PATH are set in .env, they appear as defaults in the prompts (press Enter to accept):
Creating template 'demo'
jardis_host [192.168.1.10]:
jardis_port [9091]:
jardis_env [demo-user]:
users_workspace_path [/home/kokos/users-workspace]:
Enter repo names one per line (empty line to finish):
repo: kokos-dsl-showcase
repo:
Template 'demo' created:
demo/main.tf
demo/modules -> ../modules (symlink)
Review the generated <template-name>/main.tf, then push with ./push-template.sh <template-name>.
Skip this step if coder is already installed (coder version to check).
curl -fsSL https://coder.com/install.sh | shThen log in against your Coder instance:
coder login http://localhost:7080./push-template.sh <template-name>The script uses
cp -rLto dereference the<template-name>/modulessymlink before uploading, because Coder's provisioner does not follow symlinks.
| Change | Stop/start enough? |
|---|---|
| Jardis extension version | Yes |
| ibmi-languages extension | Yes (reinstalled on every start) |
jardis_host / jardis_port / jardis_env in main.tf |
Push template, then stop/start |
users_workspace_path in main.tf |
No — destroy and recreate (also restart docker-compose) |
| Bind mount path changes | No — destroy and recreate |
| Repo list changes | Yes (new repos are cloned on next start) |
Edit JARDIS_VERSION and JARDIS_VSIX in modules/jardis-workspace/main.tf, then push the template. Existing workspaces pick up the new version on next start.
Change the image tag in docker-compose.yml and restart:
docker compose up -d