diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..f1d06e8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,65 @@ +# AGENTS.md + +## Project overview + +This repository builds and publishes a Docker image for [OpenCode](https://opencode.ai), the open source AI coding agent. The image runs OpenCode in headless server mode (`opencode serve`) and is automatically rebuilt and pushed to Docker Hub (`jcabillot/opencode`) every night by a Jenkins pipeline. + +## Repository structure + +``` +. +├── Dockerfile # Image definition +├── Jenkinsfile # CI/CD pipeline (nightly build + Docker Hub push) +└── README.md # Usage documentation +``` + +## Dockerfile conventions + +- **Base image**: `node:24-alpine` — use the latest Node.js LTS Alpine image. +- **Install**: `npm i -g opencode-ai` — installs OpenCode globally. +- **Version check**: `RUN opencode --version` after install to validate the build and record the installed version in build logs. +- **Dedicated user**: a non-root `opencode` user and group are created with `addgroup`/`adduser`. All runtime steps run as this user. +- **Entrypoint**: `["opencode", "serve"]` — the container always starts the HTTP server. + +## Jenkinsfile conventions + +- The pipeline runs on a `@midnight` cron trigger for nightly rebuilds. +- Build uses `--no-cache --pull` to always fetch the latest base image and package version. +- Docker Hub credentials are stored under the `dockerhub_jcabillot` Jenkins credential ID. +- The image is published as `jcabillot/opencode` (no explicit tag = `latest`). + +## Useful commands + +```bash +# Build the image locally +docker build -t opencode . + +# Run the server on all interfaces +docker run -it -p 4096:4096 opencode --hostname 0.0.0.0 + +# Run with a project mounted +docker run -it -p 4096:4096 \ + -v $(pwd):/home/opencode/project \ + opencode --hostname 0.0.0.0 + +# Protect with a password +docker run -it -p 4096:4096 \ + -e OPENCODE_SERVER_PASSWORD=secret \ + opencode --hostname 0.0.0.0 +``` + +## opencode serve options + +| Flag | Default | Description | +|------|---------|-------------| +| `--port` | `4096` | Port to listen on | +| `--hostname` | `127.0.0.1` | Hostname to bind | +| `--mdns` | `false` | Enable mDNS discovery | +| `--cors` | `[]` | Additional allowed browser origins | + +## Environment variables + +| Variable | Description | +|----------|-------------| +| `OPENCODE_SERVER_PASSWORD` | Enables HTTP basic auth | +| `OPENCODE_SERVER_USERNAME` | Overrides the default username (`opencode`) | diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..06c9ae8 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,19 @@ +FROM node:24 + +RUN apt-get update && apt-get upgrade -y && \ + rm -rf /var/lib/apt/lists/* + +RUN groupadd -r opencode && useradd -m -r -g opencode opencode + +RUN npm update -g && \ + npm install -g opencode-ai && \ + npm cache clean --force + +COPY --chmod=755 opencode-attach /usr/local/bin/opencode-attach + +USER opencode +WORKDIR /home/opencode + +RUN opencode --version + +ENTRYPOINT ["opencode"] diff --git a/Jenkinsfile b/Jenkinsfile new file mode 100644 index 0000000..1cbe7be --- /dev/null +++ b/Jenkinsfile @@ -0,0 +1,38 @@ +pipeline { + environment { + registry = 'https://registry.hub.docker.com' + registryCredential = 'dockerhub_jcabillot' + dockerImage = 'jcabillot/opencode' + } + + agent any + + triggers { + cron('@midnight') + } + + stages { + stage('Clone repository') { + steps{ + checkout scm + } + } + + stage('Build image') { + steps{ + sh 'docker build --force-rm=true --no-cache=true --pull -t ${dockerImage} .' + } + } + + stage('Deploy Image') { + steps{ + script { + withCredentials([usernamePassword(credentialsId: 'dockerhub_jcabillot', usernameVariable: 'DOCKER_USER', passwordVariable: 'DOCKER_PASS')]) { + sh 'docker login --username ${DOCKER_USER} --password ${DOCKER_PASS}' + sh 'docker push ${dockerImage}' + } + } + } + } + } +} diff --git a/README.md b/README.md index 711a069..af44136 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,95 @@ -# opencode +# opencode Docker +Docker image for running [OpenCode](https://opencode.ai) as a headless HTTP server. + +## What is OpenCode? + +OpenCode is an open source AI coding agent available as a terminal interface, desktop app, or IDE extension. This image runs it in server mode (`opencode serve`), exposing an HTTP API that clients can connect to. + +## Pull + +```bash +docker pull jcabillot/opencode +``` + +The image is rebuilt and pushed to Docker Hub automatically every night via the Jenkins pipeline. + +## Build locally + +```bash +docker build -t opencode . +``` + +## Run + +```bash +docker run -it -p 4096:4096 jcabillot/opencode +``` + +By default the server listens on `127.0.0.1:4096`. To expose it on all interfaces: + +```bash +docker run -it -p 4096:4096 jcabillot/opencode --hostname 0.0.0.0 +``` + +### Mount a project + +```bash +docker run -it -p 4096:4096 \ + -v $(pwd):/home/opencode/project \ + jcabillot/opencode --hostname 0.0.0.0 +``` + +### Secure with a password + +```bash +docker run -it -p 4096:4096 \ + -e OPENCODE_SERVER_PASSWORD=your-password \ + jcabillot/opencode --hostname 0.0.0.0 +``` + +Authentication uses HTTP basic auth. The default username is `opencode`, override it with `OPENCODE_SERVER_USERNAME`. + +## Troubleshooting helper + +The image includes an `opencode-attach` wrapper in `PATH` that runs: + +```bash +opencode attach -p "${OPENCODE_SERVER_PASSWORD}" "${OPENCODE_API_URL}" +``` + +Example: + +```bash +export OPENCODE_SERVER_PASSWORD=your-password +export OPENCODE_API_URL=http://127.0.0.1:4096 +opencode-attach +``` + +## API + +Once running, the server exposes an OpenAPI 3.1 spec at: + +``` +http://localhost:4096/doc +``` + +Key endpoints: + +| Method | Path | Description | +|--------|------|-------------| +| `GET` | `/global/health` | Health check and version | +| `GET` | `/session` | List sessions | +| `POST` | `/session` | Create a session | +| `POST` | `/session/:id/message` | Send a message | +| `GET` | `/event` | Server-sent events stream | + +See the [OpenCode server docs](https://opencode.ai/docs/server/) for the full API reference. + +## Image details + +- **Base image**: `node:24` (Debian) +- **Install**: `opencode-ai` via npm global install +- **User**: dedicated non-root `opencode` user +- **Entrypoint**: `opencode serve` +- **Default port**: `4096` diff --git a/opencode-attach b/opencode-attach new file mode 100644 index 0000000..b59c417 --- /dev/null +++ b/opencode-attach @@ -0,0 +1,7 @@ +#!/bin/sh +set -eu + +: "${OPENCODE_SERVER_PASSWORD:?OPENCODE_SERVER_PASSWORD is required}" +: "${OPENCODE_API_URL:?OPENCODE_API_URL is required}" + +exec opencode attach -p "${OPENCODE_SERVER_PASSWORD}" "${OPENCODE_API_URL}" "$@"