Coder and Code-Server

Published: Apr 14, 2025 by Isaac Johnson

At GCP Next25 I gravitated towards a smaller booth for Coder. The logo looked familiar but I couldn’t readily place it untill I realized this is the same Coder app I’ve been using for years in my cluster.

/content/images/2025/04/coder-01.png

Like all vendors and booths, they had some AI features in the pipe and some recent updates worth checking out.

Today, let’s first tackle updating our Coder from v4.98.2, which is still pretty current, to the latest 4.99 release.

Then we can explore new features

Updating

The reason my codeserver was relatively recent is I pull from latest:

$ kubectl get deployment code-server-deployment -o yaml | grep image | tail -n2
        image: lscr.io/linuxserver/code-server:latest
        imagePullPolicy: Always

And just recently we can see the pod was rotated:

$ kubectl get deployment code-server-deployment -o yaml | tail -n 19
status:
  availableReplicas: 1
  conditions:
  - lastTransitionTime: "2024-03-02T14:33:12Z"
    lastUpdateTime: "2024-05-18T16:26:27Z"
    message: ReplicaSet "code-server-deployment-69fb568f4b" has successfully progressed.
    reason: NewReplicaSetAvailable
    status: "True"
    type: Progressing
  - lastTransitionTime: "2025-04-02T22:56:55Z"
    lastUpdateTime: "2025-04-02T22:56:55Z"
    message: Deployment has minimum availability.
    reason: MinimumReplicasAvailable
    status: "True"
    type: Available
  observedGeneration: 2
  readyReplicas: 1
  replicas: 1
  updatedReplicas: 1

I’m willing to bet that if I look at my current version:

/content/images/2025/04/coder-02.png

Then rotate the pod

$ kubectl delete po code-server-deployment-69fb568f4b-h5mhq
pod "code-server-deployment-69fb568f4b-h5mhq" deleted
$ kubectl get po -A | grep code-server
default                 code-server-deployment-69fb568f4b-7q7bd              0/1     ContainerCreating   0                   14s

I can see the latest version

/content/images/2025/04/coder-03.png

Cloud Code (GCP)

Let’s look first at what we can do with the “Cloud Code” plugin.

We first need to auth to GCP

/content/images/2025/04/coder-04.png

I then have access to a variety of offerings in GCP

/content/images/2025/04/coder-05.png

For instance, I could create a GCP SM secret

/content/images/2025/04/coder-06.png

Once created, I can see some details on how to access

/content/images/2025/04/coder-07.png

For which I can easily verify in the cloud console

/content/images/2025/04/coder-08.png

Gemini Code Assist

We can also access Gemini Code Assist from the Gemini menu as well

/content/images/2025/04/coder-09.png

Here, let’s have it create a python app that can show the secret we created:

We can see it assumed a few things (like the session had the gcloud binary) but it was something easily to address.

I could now see a python script that could fetch the value:

/content/images/2025/04/coder-10.png

We can also see Gemini Code Assist gave us three ways to auth:

/content/images/2025/04/coder-11.png

If I were to build an app to run in my K3s or Docker, i would likely use method 2 and create an SA with JSON to use.

Continue Dev

We have yet another way to engage with Gemini - via the “Continue.dev” plugin.

I’ll start by going to the GCP AI Studio and using or creating a new API Key

/content/images/2025/04/coder-12.png

I can now apply that in the Config to add, for instance, Gemini 2.0 Flash

/content/images/2025/04/coder-13.png

Which works well

However, if I try and use Gemini Pro. I’ll get an error about “models/gemini-pro is not found for API version v1beta”

/content/images/2025/04/coder-15.png

However, if I go to models, I can pick from another model available at my tier

/content/images/2025/04/coder-16.png

As you can see, that worked

/content/images/2025/04/coder-17.png

I do not see cost immediately reflected, but I would expect something later, even if just a few cents

/content/images/2025/04/coder-18.png

Pushing Code

At this point we have a file but it’s entirely local to this containerized code-server

/content/images/2025/04/coder-19.png

Let’s create a new repo in Github

/content/images/2025/04/coder-20.png

Once, created, we can follow the steps to push our code

/content/images/2025/04/coder-21.png

Here we can see how we can push up master (or main)

Coder

We reviewed “code-server” above, which is excellent and I use it often.

However, Coder (the company) also has a “Coder” app and server different from “Code-server”.

Let’s follow this guide to setting it up locally

I’ll first create a namespace then install an off-the-shelf PostgreSQL for Code to use:

$ kubectl create namespace coder
namespace/coder created

$ helm repo add bitnami https://charts.bitnami.com/bitnami
"bitnami" already exists with the same configuration, skipping

$ helm install coder-db bitnami/postgresql --namespace coder --set auth.username=coder --set auth.password=coder --set auth.database=coder --set persistence.size=10Gi
NAME: coder-db
LAST DEPLOYED: Mon Apr 14 14:03:29 2025
NAMESPACE: coder
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
CHART NAME: postgresql
CHART VERSION: 16.4.5
APP VERSION: 17.2.0

Did you know there are enterprise versions of the Bitnami catalog? For enhanced secure software supply chain features, unlimited pulls from Docker, LTS support, or application customization, see Bitnami Premium or Tanzu Application Catalog. See https://www.arrow.com/globalecs/na/vendors/bitnami for more information.

** Please be patient while the chart is being deployed **

PostgreSQL can be accessed via port 5432 on the following DNS names from within your cluster:

    coder-db-postgresql.coder.svc.cluster.local - Read/Write connection

To get the password for "postgres" run:

    export POSTGRES_ADMIN_PASSWORD=$(kubectl get secret --namespace coder coder-db-postgresql -o jsonpath="{.data.postgres-password}" | base64 -d)

To get the password for "coder" run:

    export POSTGRES_PASSWORD=$(kubectl get secret --namespace coder coder-db-postgresql -o jsonpath="{.data.password}" | base64 -d)

To connect to your database run the following command:

    kubectl run coder-db-postgresql-client --rm --tty -i --restart='Never' --namespace coder --image docker.io/bitnami/postgresql:17.2.0-debian-12-r8 --env="PGPASSWORD=$POSTGRES_PASSWORD" \
      --command -- psql --host coder-db-postgresql -U coder -d coder -p 5432

    > NOTE: If you access the container using bash, make sure that you execute "/opt/bitnami/scripts/postgresql/entrypoint.sh /bin/bash" in order to avoid the error "psql: local user with ID 1001} does not exist"

To connect to your database from outside the cluster execute the following commands:

    kubectl port-forward --namespace coder svc/coder-db-postgresql 5432:5432 &
    PGPASSWORD="$POSTGRES_PASSWORD" psql --host 127.0.0.1 -U coder -d coder -p 5432

WARNING: The configured password will be ignored on new installation in case when previous PostgreSQL release was deleted through the helm command. In that case, old PVC will have an old password, and setting it through helm won't take effect. Deleting persistent volumes (PVs) will solve the issue.

WARNING: There are "resources" sections in the chart not set. Using "resourcesPreset" is not recommended for production. For production installations, please set the following values according to your workload needs:
  - primary.resources
  - readReplicas.resources
+info https://kubernetes.io/docs/concepts/configuration/manage-resources-containers/

I then create a secret that the Coder helm chart will consume for Database credentials

$ kubectl create secret generic coder-db-url -n coder --from-literal=url="postgres://coder:coder@coder-db-postgresql.coder.svc.cluster.local:5432/coder?sslmode=disable"
secret/coder-db-url created

I’ll create a quick values file that has this secret listed:

$ cat values.yaml
coder:
  # You can specify any environment variables you'd like to pass to Coder
  # here. Coder consumes environment variables listed in
  # `coder server --help`, and these environment variables are also passed
  # to the workspace provisioner (so you can consume them in your Terraform
  # templates for auth keys etc.).
  #
  # Please keep in mind that you should not set `CODER_HTTP_ADDRESS`,
  # `CODER_TLS_ENABLE`, `CODER_TLS_CERT_FILE` or `CODER_TLS_KEY_FILE` as
  # they are already set by the Helm chart and will cause conflicts.
  env:
    - name: CODER_PG_CONNECTION_URL
      valueFrom:
        secretKeyRef:
          # You'll need to create a secret called coder-db-url with your
          # Postgres connection URL like:
          # postgres://coder:password@postgres:5432/coder?sslmode=disable
          name: coder-db-url
          key: url
    # For production deployments, we recommend configuring your own GitHub
    # OAuth2 provider and disabling the default one.
    - name: CODER_OAUTH2_GITHUB_DEFAULT_PROVIDER_ENABLE
      value: "false"

    # (Optional) For production deployments the access URL should be set.
    # If you're just trying Coder, access the dashboard via the service IP.
    # - name: CODER_ACCESS_URL
    #   value: "https://coder.example.com"

  #tls:
  #  secretNames:
  #    - my-tls-secret-name

Then I can install the latest

$ helm install coder coder-v2/coder --namespace coder --values values.yaml --version 2.21.0
NAME: coder
LAST DEPLOYED: Mon Apr 14 14:06:11 2025
NAMESPACE: coder
STATUS: deployed
REVISION: 1
TEST SUITE: None
NOTES:
Enjoy Coder! Please create an issue at https://github.com/coder/coder if you run
into any problems! :)

Next, I watch for the pods to come up

$ kubectl get po -n coder
NAME                     READY   STATUS              RESTARTS   AGE
coder-db-postgresql-0    1/1     Running             0          3m
coder-7984578b8b-tvzgr   0/1     ContainerCreating   0          18s

$ kubectl get po -n coder
NAME                     READY   STATUS    RESTARTS   AGE
coder-db-postgresql-0    1/1     Running   0          3m28s
coder-7984578b8b-tvzgr   1/1     Running   0          46s

I can now port-forward to the service

$ kubectl get svc -n coder
NAME                     TYPE           CLUSTER-IP      EXTERNAL-IP   PORT(S)        AGE
coder-db-postgresql-hl   ClusterIP      None            <none>        5432/TCP       3m42s
coder-db-postgresql      ClusterIP      10.43.96.242    <none>        5432/TCP       3m42s
coder                    LoadBalancer   10.43.241.249   <pending>     80:32239/TCP   60s

$ kubectl port-forward -n coder svc/coder 8088:80
Forwarding from 127.0.0.1:8088 -> 8080
Forwarding from [::1]:8088 -> 8080

I’m presented with a setup

/content/images/2025/04/coder-23.png

I’m now in a workspace dashboard

/content/images/2025/04/coder-24.png

Before I can create a workspace, I need to create a template

/content/images/2025/04/coder-25.png

I started with a Docker template

/content/images/2025/04/coder-26.png

But it failed to connect to the docker daemon (likely because Coder wasn’t launched in a priveledged mode)

/content/images/2025/04/coder-27.png

Let’s try Kubernetes instead. Set the namespace

/content/images/2025/04/coder-28.png

Then we can deploy. What threw me off is the output is terraform/opentofu

/content/images/2025/04/coder-29.png

Is that meant to explain what it is making? Or something I’m expected to execute?

I’ll try and use it with a new workspace

/content/images/2025/04/coder-30.png

Which shows it’s using TF 1.11

/content/images/2025/04/coder-31.png

In short order I see it running

/content/images/2025/04/coder-32.png

If I click “code-server” it pops up a fresh coder instance

/content/images/2025/04/coder-33.png

If I do “VS Code Desktop”

/content/images/2025/04/coder-34.png

I see it try and use SSH - I doubt this will work because I’m only port-forwarding on HTTP

/content/images/2025/04/coder-35.png

Lastly, I can just kick open a Terminal as well if command line is what I’m after

/content/images/2025/04/coder-36.png

Timeline

I thought it was interesting we can see a timeline for how long it took to build this Workspace.

/content/images/2025/04/coder-37.png

I’m not sure what I would do with that information, but there it is.

Stopping

I can also just stop a Coder workspace. This is feeling very much like Github Codespaces

/content/images/2025/04/coder-38.png

Once I stop it

/content/images/2025/04/coder-39.png

I can always open it to start it again

/content/images/2025/04/coder-40.png

Ingress

Let’s add an Azure DNS record

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -a 75.73.224.240 -n coder
{
  "ARecords": [
    {
      "ipv4Address": "75.73.224.240"
    }
  ],
  "TTL": 3600,
  "etag": "8a170119-c778-473a-af11-ce2df2cfb15c",
  "fqdn": "coder.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/coder",
  "name": "coder",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

In the values I’m going to set the Ingress details

  ingress:
    annotations:
      cert-manager.io/cluster-issuer: azuredns-tpkpw
    apiVersion: ""
    enabled: true
    extraHosts: []
    extraPaths: []
    extraRules: []
    extraTls: []
    hostname: coder.tpk.pw
    ingressClassName: nginx
    path: /
    pathType: ImplementationSpecific
    secrets:
    - codercert
    selfSigned: false
    tls: true

and change the service to a ClusterIP

  service:
    annotations: {}
    enable: true
    externalTrafficPolicy: Cluster
    httpNodePort: ""
    httpsNodePort: ""
    loadBalancerClass: ""
    loadBalancerIP: ""
    sessionAffinity: None
    type: ClusterIP

I’ll now try an upgrade to see if it picks it up

$ helm upgrade coder coder-v2/coder --namespace coder --values coder.values.yaml --version 2.21.0
coalesce.go:220: warning: cannot overwrite table with non table for coder.coder.ingress.tls (map[enable:false secretName: wildcardSecretName:])
coalesce.go:220: warning: cannot overwrite table with non table for coder.coder.ingress.tls (map[enable:false secretName: wildcardSecretName:])
Release "coder" has been upgraded. Happy Helming!
NAME: coder
LAST DEPLOYED: Mon Apr 14 15:08:48 2025
NAMESPACE: coder
STATUS: deployed
REVISION: 2
TEST SUITE: None
NOTES:
Enjoy Coder! Please create an issue at https://github.com/coder/coder if you run
into any problems! :)

I see it did switch the service, but did not address the ingress

$ kubectl get svc -n coder
NAME                     TYPE        CLUSTER-IP      EXTERNAL-IP   PORT(S)    AGE
coder-db-postgresql-hl   ClusterIP   None            <none>        5432/TCP   65m
coder-db-postgresql      ClusterIP   10.43.96.242    <none>        5432/TCP   65m
coder                    ClusterIP   10.43.241.249   <none>        80/TCP     63m
$ kubectl get ingress -n coder
No resources found in coder namespace.

No bother, I can just create it myself

$ cat coder.ingress.yaml
apiVersion: networking.k8s.io/v1
kind: Ingress
metadata:
  annotations:
    cert-manager.io/cluster-issuer: azuredns-tpkpw
    ingress.kubernetes.io/ssl-redirect: "true"
    kubernetes.io/ingress.class: nginx
    kubernetes.io/tls-acme: "true"
    nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
    nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
    nginx.org/websocket-services: coder
  name: coderingress
spec:
  rules:
  - host: coder.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: coder
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - coder.tpk.pw
    secretName: coder-tls
$ kubectl apply -f ./coder.ingress.yaml -n coder
ingress.networking.k8s.io/coderingress created

When I see the cert is satisfied

$ kubectl get cert -n coder
NAME        READY   SECRET      AGE
coder-tls   False   coder-tls   27s

$ kubectl get cert -n coder
NAME        READY   SECRET      AGE
coder-tls   True    coder-tls   81s

I can now login from the proper HTTPS URL

/content/images/2025/04/coder-41.png

Which makes it handy to get at resources in my network

/content/images/2025/04/coder-42.png

But because I’m using Nginx, not a proper cloud LB or Istio, I know SSH will not work

/content/images/2025/04/coder-43.png

However - IF I was in an environment where SSH would work, I could use the Coder VS Code plugin to connect to them

/content/images/2025/04/coder-46.png

Pricing

Coder does have a premium option. Consider it “Github Codespaces” without the Github. I could very much see this valuable for On-Prem organizations.

However, there is no actual pricing listed

/content/images/2025/04/coder-44.png

I could definately see value in having logging and workspace cleanup controls, among many other premium features

/content/images/2025/04/coder-45.png

Summary

Coder is a great option for those that would want more than just the one hosted code instance that code-server would give. It actually has quite a few templates and covers the major Cloud providers including AWS, Azure, DigitalOcean and GCP

/content/images/2025/04/coder-47.png

However, one of my gripes here is it doesn’t let me specify credentials - either by way of settings in a provider or environment keys.

For instance, if we look at the Azure VM provider we can see it just assumes we are logged in to an Azure account

/content/images/2025/04/coder-48.png

That seems like a rather big limitation (as I would very likely have Coder running in a Production subscription and want my workspaces set in a Development subscription).

That said, I’m overall impressed with the offering (especially all the features in the pure OS version). It is something definately worth checking out.

Coder Codeserver OpenSource Kubernetes VSCode

Have something to add? Feedback? You can use the feedback form

Isaac Johnson

Isaac Johnson

Cloud Solutions Architect

Isaac is a CSA and DevOps engineer who focuses on cloud migrations and devops processes. He also is a dad to three wonderful daughters (hence the references to Princess King sprinkled throughout the blog).

Theme built by C.S. Rhymes