Os Apps: Dockhand and Fresh

Published: Dec 23, 2025 by Isaac Johnson

I saw this recent Marius post about Dockhand and really wanted to give it a try.

I thought it might be a small one-trick suite like other “docker managers” I’ve reviewed but this one, as I reviewed it below, showed itself to be uniquely full featured.

While there is a commercial edition, we’ll just stick with the free one. I’m not really sure if it is actually open-source as they don’t have their source on GH

I’ll also take a moment to look at a fast light editor called Fresh.

Dockhand

There are a few versions which we will circle back on, but for now let’s dig into the free OS version

We can fire this off with docker serving up on port 3000:

$ docker run -d \
  --name dockhand \
  -p 3000:3000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v dockhand_data:/app/data \
  fnsys/dockhand:latest

Opening http://localhost:3000 we see a notice on latest updates first

/content/images/2025/12/dockerhand-01.png

Since this is intended to manage multiple hosts, we need to start by adding an Environment

/content/images/2025/12/dockerhand-02.png

Here I’ll give it the name WSLLocal. I can add labels and if I wanted to view this from other machines, I would set the Public IP to the computers local IPv4

/content/images/2025/12/dockerhand-03.png

A quick “Test connection” verified it was working

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

By default, all the activity monitoring is enabled

/content/images/2025/12/dockerhand-05.png

I can enable container scanning on pull and pick the scanner to use (by default off)

/content/images/2025/12/dockerhand-06.png

For now, i can’t use notifications as I have yet to setup those channels

/content/images/2025/12/dockerhand-07.png

Clicking add now shows the “WSLLocal” environment

/content/images/2025/12/dockerhand-08.png

Registries

by default, it has Docker Hub, but let’s add our Harbor CR to it

/content/images/2025/12/dockerhand-09.png

I just need to set the root of the CR

/content/images/2025/12/dockerhand-10.png

To test, let’s search for a private container and expand the tags

/content/images/2025/12/dockerhand-11.png

I initiated a pull and scan:

/content/images/2025/12/dockerhand-12.png

Which took a few to scan for vulnerabilities:

/content/images/2025/12/dockerhand-13.png

I can see the ones found via Aqua Trivy in Harbor:

/content/images/2025/12/dockerhand-14.png

And compare with those found by Grype in Dockerhand

/content/images/2025/12/dockerhand-15.png

If one clicks on any off the rows, one will see an expanded detailed summary on the CVE

/content/images/2025/12/dockerhand-16.png

The link will still go to NIST (e.g. CVE-2023-45288) whereas Harbor will send me to Aqua (e.g. CVE-2024-41110).

I think all these tools will need to focus on moving off US government sites sooner than later. I hope to see both move to the newer EUVD. That said, when the NIST site eventually goes offline, I would expect to find URLs matching up (e.g. CVE-2023-45288 in EUVD)

Launching a container

We can actually use the Registry pane to create a new container by clicking the play icon

/content/images/2025/12/dockerhand-17.png

Reviewing my last post on Dokemon, it seems I just need a volume for data if I want to persist it.

I tried just adding a Device

/content/images/2025/12/dockerhand-18.png

But it got stuck in “created”

/content/images/2025/12/dockerhand-19.png

I went to Docker locally and saw that indeed, it won’t automatically create the directories

/content/images/2025/12/dockerhand-20.png

I’ll do a quick dir create for it

$ mkdir /tmp/dokemondata
$ chmod 777 /tmp/dokemondata

however, Docker still complained:

(HTTP code 500) server error - error gathering device information while adding custom device "/tmp/dokemondata": no such file or directory

I noticed a “failed to search images” for nginx in Docker Hub.

I then went to the Registries and clicked “edit” on Docker Hub to set my own creds

/content/images/2025/12/dockerhand-21.png

I can now see my username next to the key icon in “Docker Hub” in the registries

/content/images/2025/12/dockerhand-22.png

However, it still fails to find nginx

/content/images/2025/12/dockerhand-23.png

While I saw now errors in the docker logs for dockhand, I was suspicious as the app was performing really poorly in the browser.

I did the old turn-it-off-and-on-again

g$ docker stop dockhand
dockhand
$ docker start dockhand
dockhand

And now it is working - so it might not have been Docker Hub auth issues before. The search now comes back with logs of options

/content/images/2025/12/dockerhand-24.png

I chose a latest and clicked run. I kept it simple just giving a name and some port mappings

/content/images/2025/12/dockerhand-25.png

However, there seems to be a bug as it fails to create with a port mapping

/content/images/2025/12/dockerhand-26.png

However, creating with no port mapping then adding it after worked just fine

/content/images/2025/12/dockerhand-27.png

wait! it said it updated but then it completely deleted it!

This is a pretty bad behaviour, imho. Here you can see what I mean:

Let’s put a pin into creating and updating containers as I now have some trust issues

Activity

Viewing activity, at least we see that container was repeatedly removed

/content/images/2025/12/dockerhand-29.png

Viewing details clearly shows a “destroy” command

/content/images/2025/12/dockerhand-30.png

Schedules

By default, we have a few basic scheduled tasks to keep bloat down - namely events, logs and temporary volume cleanup

/content/images/2025/12/dockerhand-31.png

Stacks

Stacks launch with Docker Compose are listed in “Stacks”

/content/images/2025/12/dockerhand-32.png

We can “create” a stack here. I will test my luck and do Nginx again with a port mapping

/content/images/2025/12/dockerhand-33.png

That launched just fine and I can see a 403 on root (it is a blank NGinx so i expect that - the fact we see “nginx/1.29.4” is the tell it is functioning)

/content/images/2025/12/dockerhand-34.png

Authentication

To add auth, we first need to add a user:

/content/images/2025/12/dockerhand-35.png

As we see, this is our first instance of a ‘pay’ feature in that RBAC is relegated to Enterprise licensed versions. I feel that is fair.

Once we have at least one user, we can enable Authentication

/content/images/2025/12/dockerhand-36.png

Further actions showed errors and a refresh showed i needed to now login

/content/images/2025/12/dockerhand-37.png

Once logged in, I can see my user in the lower left

/content/images/2025/12/dockerhand-38.png

Notifications

For Notifications, we can choose SMTP (email) or Apprise (Webhooks)

/content/images/2025/12/dockerhand-39.png

Ever since Sendgrid quit, I tend to use either Resend, Gmail or SES.

let’s use SES on this one

/content/images/2025/12/dockerhand-40.png

However, this failed to send (and I know my settings are correct)

/content/images/2025/12/dockerhand-41.png

the Logs suggest it might be auth or lack of subject

/content/images/2025/12/dockerhand-42.png

However, gmail SMTP

/content/images/2025/12/dockerhand-43.png

seemed to work without issue

/content/images/2025/12/dockerhand-44.png

Git Repos

For private repos, we need to first set a token or SSH key

/content/images/2025/12/dockerhand-45.png

I can now add a repo and set the credential

/content/images/2025/12/dockerhand-46.png

Now when launching a stack, I can use the GIT repo to fetch the docker compose

As an aside, i again got errors

/content/images/2025/12/dockerhand-47.png

that a restart of Dockerhand in docker solved.

/content/images/2025/12/dockerhand-48.png

A slight issue I found is this docker-compose has some variables that need to be set and there is no real way to do that in this UI. So I got caught in an endless restart

/content/images/2025/12/dockerhand-49.png

Push to registry

One rather neat feature is that if we have setup a private CR, we can use Dockerhand to help push things up.

This can be useful for local development:

Shell

Another nice features of Dockhand is to just shell into a container.

We go to shell, then pick a running container

/content/images/2025/12/dockerhand-50.png

I can hop into the Nginx container, for instance and see where the default HTML folder is

/content/images/2025/12/dockerhand-51.png

I had hoped to show a nice example of creating and updating the HTML page off the Nginx container, but they rendered the filesystem RO so I can’t:

/content/images/2025/12/dockerhand-52.png

Logs

We can view recent logs of any container and follow them if desired.

/content/images/2025/12/dockerhand-53.png

I like the simple search feature which for most cases would solve our needs. However, there is a download button to save them if needed

Volumes

Volumes offers a simple dynamic sort table

/content/images/2025/12/dockerhand-54.png

The file browser part really surprised me. I have never seen that before.

It appears to mount the volume on Dockerhand and let use browse the files

/content/images/2025/12/dockerhand-55.png

i can also export as a tar file

/content/images/2025/12/dockerhand-56.png

Which downloads it locally

/content/images/2025/12/dockerhand-57.png

I think the only feature I would really want is some kind of idea of size of these. It makes deciding when to prune a much easier decision.

We only see basic information in the details view

/content/images/2025/12/dockerhand-58.png

Networks

The networks page shows all networks presently in existence

/content/images/2025/12/dockerhand-59.png

Viewing the details shows things like connected containers, gateways and subnets

/content/images/2025/12/dockerhand-60.png

We can use the “connect container” action to connect an existing container to that network.

/content/images/2025/12/dockerhand-61.png

I can see that being useful for, say, restoring a database, launching the restored copy directly and then adding it to the network, or using a cloned copy of a Valkey store and adding it in for key storage.

Profile page

The profile page lets us set things like our Display name and icon

/content/images/2025/12/dockerhand-62.png

we can also use it to change the password if needed.

Here we see the second feature behind the paywall, MFA.

However, I could easily support MFA by just using AAD/Entra ID, a GCP app or Okta

/content/images/2025/12/dockerhand-63.png

Yes, the Role mapping, as i would expect, is an enterprise feature, but then i could ensure MFA by way of federated Identity management.

Linux

Let’s take a minute and get off WSL and use a proper Ubuntu Docker host.

I’m already using 3000 for zipline so let’s use port 3050 in this case

$ docker run -d \
  --name dockhand \
  -p 3050:3000 \
  -v /var/run/docker.sock:/var/run/docker.sock \
  -v dockhand_data:/app/data \
  fnsys/dockhand:latest

Though, when I add environments I’ll set the PublicIP so i can connect from my primary desktop

/content/images/2025/12/dockerhand-64.png

Which then lets me click the forwarding link to access

/content/images/2025/12/dockerhand-65.png

Because I’m running Prunemate here, there are not a lot of stopped and stale containers to cleanup

/content/images/2025/12/dockerhand-66.png

Exposing with TLS

I’ll first need an A-Record for this. I’ll use Azure DNS

$ az account set --subscription "Pay-As-You-Go" && az network dns record-set a add-record -g idjdnsrg -z tpk.pw -
a 75.72.233.202 -n dockhand
{
  "ARecords": [
    {
      "ipv4Address": "75.72.233.202"
    }
  ],
  "TTL": 3600,
  "etag": "84d6a7f1-3716-4ce1-b3a9-a0d084e541be",
  "fqdn": "dockhand.tpk.pw.",
  "id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/dockhand",
  "name": "dockhand",
  "provisioningState": "Succeeded",
  "resourceGroup": "idjdnsrg",
  "targetResource": {},
  "trafficManagementProfile": {},
  "type": "Microsoft.Network/dnszones/A"
}

Next, I’ll use a YAML manifest for an endpoint, service and ingress

$ cat dockhand.yaml
apiVersion: v1
kind: Endpoints
metadata:
  name: dockhand-external-ip
subsets:
- addresses:
  - ip: 192.168.1.143
  ports:
  - name: dhndint
    port: 3050
    protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
  name: dockhand-external-ip
spec:
  clusterIP: None
  clusterIPs:
  - None
  internalTrafficPolicy: Cluster
  ipFamilies:
  - IPv4
  - IPv6
  ipFamilyPolicy: RequireDualStack
  ports:
  - name: dockhand
    port: 80
    protocol: TCP
    targetPort: 3050
  sessionAffinity: None
  type: ClusterIP
---
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: dockhand-external-ip
  generation: 1
  name: dockhandingress
spec:
  rules:
  - host: dockhand.tpk.pw
    http:
      paths:
      - backend:
          service:
            name: dockhand-external-ip
            port:
              number: 80
        path: /
        pathType: ImplementationSpecific
  tls:
  - hosts:
    - dockhand.tpk.pw
    secretName: dockhand-tls

$ kubectl apply -f ./dockhand.yaml
endpoints/dockhand-external-ip created
service/dockhand-external-ip created
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/dockhandingress created

As that loads, I’ll make sure to add a user and enable auth

/content/images/2025/12/dockerhand-67.png

And soon thereafter I get a proper HTTPS website I can reach externally

/content/images/2025/12/dockerhand-68.png

Fresh Editor

Fresh has been on my list from a TLDR DevOps post from some time.

To install, we can just use Homebrew

$ brew tap sinelaw/fresh
==> Auto-updating Homebrew...
Adjust how often this is run with `$HOMEBREW_AUTO_UPDATE_SECS` or disable with
`$HOMEBREW_NO_AUTO_UPDATE=1`. Hide these hints with `$HOMEBREW_NO_ENV_HINTS=1` (see `man brew`).
==> Downloading https://ghcr.io/v2/homebrew/core/portable-ruby/blobs/sha256:63a9c333137e7cf8a568e0f37ec6802c22fbbdd5839f4825c863c26e415d2947
######################################################################## 100.0%
==> Pouring portable-ruby-3.4.8.x86_64_linux.bottle.tar.gz
... snip ...



$ brew install fresh-editor
==> Fetching downloads for: fresh-editor
✔︎ Bottle Manifest fresh-editor (0.1.58)                                                                                             [Downloaded    7.5KB/  7.5KB]
✔︎ Bottle fresh-editor (0.1.58)                                                                                                      [Downloaded   46.7MB/ 46.7MB]
==> Pouring fresh-editor--0.1.58.x86_64_linux.bottle.tar.gz
🍺  /home/linuxbrew/.linuxbrew/Cellar/fresh-editor/0.1.58: 9 files, 187.6MB
==> Running `brew cleanup fresh-editor`...
... snip ...

I have no idea why I enjoy this as much as a I do. I think it harkens back to the old windows text editor from the Win 3.1 days

There is a more complete web site for Fresh that shows a few of its features like a Language Server Protocol

Though I found that failed to work, at least in WSL

/content/images/2025/12/dockerhand-69.png

I’m not really sure if I would give up VIM for something like this, but it is an interesting option.

Summary

Today we did a big deep dive into Dockhand, looking at all of its features in the Open-Source/Community edition before setting up TLS via Kubernetes endpoints.

I didn’t really dig into Federated IDP but it should be easy to setup once exposed (you do need a callback URL for Google/Okta/AAD to which to redirect).

I also took a quick glance at Fresh, a lightweight editor.

Let’s take a quick look at the Free vs Enterprise Dockhand features:

/content/images/2025/12/dockerhand-70.png

It is basically $42/mo/host for the SMB model, but the moment you want LDAP/AD integration or RBAC (and I would assume in an enterprise you want RBAC), then we are up to $1500/year/host ($125/mo).

I think that is a bit steep. In my humble opinion, the prices should be about half that to make sense.

However, one other feature I didn’t explore was their Hawser agent which would allow one to have a unified view of all Docker hosts:

/content/images/2025/12/dockerhand-71.png

So, perhaps if one thinks of a single instance that aggregates many hosts (provided they don’t charge for those other environments), then those prices might make sense.

Still, it’s an amazing product and one I’m going to try using for a while on some of my local docker hosts.

Though, lest you thought I forgot, I did test the container ports on the newer Ubuntu host and it too gave that JSON GO struct error so it was not WSL related:

/content/images/2025/12/dockerhand-72.png

fresh dockhand opensource kubernetes docker

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