Published: Jul 17, 2025 by Isaac Johnson
I’ve had a few creative apps on my list to checkout for some time. The first is Krita which has a LinuxServer group container. The other is Inkscape which also can be exposed as a container. I’ll also take a moment and just try the installable fat clients in Windows.
Lastly, akin to Filegator and other file serving apps, we’ll check out Zipline which is a very simple clean open-source file sharing app. I’ll explore deploying in Kubernetes, exposing with a TLS ingress and how to use it.
Let’s start with Krita.
Krita
Marius posted about this free and open-source painting program, Krita.
The Linuxserver docs suggest we can use Docker compose
---
services:
krita:
image: lscr.io/linuxserver/krita:latest
container_name: krita
security_opt:
- seccomp:unconfined #optional
environment:
- PUID=1000
- PGID=1000
- TZ=Etc/UTC
volumes:
- /path/to/config:/config
ports:
- 3000:3000
- 3001:3001
restart: unless-stopped
That should convert nicely to a k8s manifest
---
apiVersion: v1
kind: PersistentVolumeClaim
metadata:
name: krita-config-pvc
spec:
accessModes:
- ReadWriteOnce
storageClassName: local-path
resources:
requests:
storage: 1Gi
---
apiVersion: apps/v1
kind: Deployment
metadata:
name: krita
spec:
replicas: 1
selector:
matchLabels:
app: krita
template:
metadata:
labels:
app: krita
spec:
containers:
- name: krita
image: lscr.io/linuxserver/krita:latest
ports:
- containerPort: 3000
- containerPort: 3001
env:
- name: PUID
value: "1000"
- name: PGID
value: "1000"
- name: TZ
value: "America/Chicago"
volumeMounts:
- name: config
mountPath: /config
securityContext:
seccompProfile:
type: Unconfined
volumes:
- name: config
persistentVolumeClaim:
claimName: krita-config-pvc
---
apiVersion: v1
kind: Service
metadata:
name: krita
spec:
selector:
app: krita
ports:
- protocol: TCP
port: 80
targetPort: 3000
Which should be easy to apply
$ kubectl apply -f ./krita.yaml
persistentvolumeclaim/krita-config-pvc created
deployment.apps/krita created
service/krita created
I can now port-forward to the service
$ kubectl port-forward svc/krita 8888:80
Forwarding from 127.0.0.1:8888 -> 3000
Forwarding from [::1]:8888 -> 3000
Handling connection for 8888
Handling connection for 8888
Handling connection for 8888
Handling connection for 8888
Handling connection for 8888
there is an interesting page on launch
but soon we have the main dashboard for the app
I noticed the new document dimensions match the browser window we are using
I’m remote in low-bandwidth presently so there is a bit of a delay, but man, this is giving me real Photoshop ~v4 vibes
Since I really wasn’t digging on the latency, I decided to pivot back to local as this really is best as a local app
I created a docker compose file according to their docs
builder@LuiGi:~/Workspaces/OSLocal/krita$ cat docker-compose.yaml
services:
krita:
image: lscr.io/linuxserver/krita:latest
container_name: krita
security_opt:
- seccomp:unconfined #optional
environment:
- PUID=1000
- PGID=1000
- TZ=America/Chicago
volumes:
- /home/builder/Workspaces/OSLocal/krita/config:/config
ports:
- 3000:3000
- 3001:3001
restart: unless-stopped
then fired it up
builder@LuiGi:~/Workspaces/OSLocal/krita$ docker compose up
[+] Running 14/14
✔ krita Pulled 130.0s
✔ a5e3ea2009e2 Pull complete 2.9s
✔ e1cde46db0e1 Pull complete 3.0s
✔ 46f2d9bbcff7 Pull complete 3.1s
✔ f2d94bf0c7ba Pull complete 3.2s
✔ 0469477658fc Pull complete 3.4s
✔ 35e63de34f77 Pull complete 6.2s
✔ 681cde493f59 Pull complete 6.4s
✔ 63ef96ffaedd Pull complete 8.9s
✔ 1c6d24f55ad9 Pull complete 10.6s
✔ 109b5f323c14 Pull complete 112.0s
✔ fd6c3b0eb1fb Pull complete 112.2s
✔ 827fa7ea52bc Pull complete 126.0s
✔ 206f67857dfb Pull complete 126.1s
[+] Running 2/2
✔ Network krita_default Created 0.2s
✔ Container krita Created 0.4s
Attaching to krita
krita | [migrations] started
krita | [migrations] no migrations found
krita | ───────────────────────────────────────
krita |
krita | ██╗ ███████╗██╗ ██████╗
krita | ██║ ██╔════╝██║██╔═══██╗
krita | ██║ ███████╗██║██║ ██║
krita | ██║ ╚════██║██║██║ ██║
krita | ███████╗███████║██║╚██████╔╝
krita | ╚══════╝╚══════╝╚═╝ ╚═════╝
krita |
krita | Brought to you by linuxserver.io
krita | ───────────────────────────────────────
krita |
krita | To support LSIO projects visit:
krita | https://www.linuxserver.io/donate/
krita |
krita | ───────────────────────────────────────
krita | GID/UID
krita | ───────────────────────────────────────
krita |
krita | User UID: 1000
krita | User GID: 1000
krita | ───────────────────────────────────────
krita | ........+.+......+..+.+..+...+.........+++++++++++++++++++++++++++++++++++++++*...+............+++++++++++++++++++++++++++++++++++++++*....+.+.........+.........+..+.............+..+....+........+.+.....+....+...+.................+.+......+........+..................+...+..........+.....+.........+.+......++++++
krita | ......+...+...+++++++++++++++++++++++++++++++++++++++*..+++++++++++++++++++++++++++++++++++++++*....+......+.........+......+..+.............+..+...+...+....+.....+..........+..+.+.....+...+......+...................+......+......+..+...+...........................+.......+...........+....+...+......+..+.......+...+....................+.......+......+..+.........+....+....................+.......+.........+...+..+...............+...+.......+.........+.....+..........+..++++++
krita | -----
krita | [custom-init] No custom files found, skipping...
krita | [ls.io-init] done.
krita | Openbox-Message: Failed to open the display from the DISPLAY environment variable.
krita | _XSERVTransmkdir: ERROR: euid != 0,directory /tmp/.X11-unix will not be created.
krita |
krita | Xvnc KasmVNC 1.3.3 - built Jun 28 2025 19:01:22
krita | Copyright (C) 1999-2018 KasmVNC Team and many others (see README.me)
krita | See http://kasmweb.com for information on KasmVNC.
krita | Underlying X server release 12101014
krita |
krita | [mi] mieq: warning: overriding existing handler 0 with 0x5eced9fe3a90 for event 2
krita | [mi] mieq: warning: overriding existing handler 0 with 0x5eced9fe3a90 for event 3
krita | 19
krita | Obt-Message: Xinerama extension is not present on the server
krita | QStandardPaths: wrong permissions on runtime directory /config/.XDG, 0755 instead of 0700
krita | QStandardPaths: wrong permissions on runtime directory /config/.XDG, 0755 instead of 0700
krita | QStandardPaths: wrong permissions on runtime directory /config/.XDG, 0755 instead of 0700
krita | QStandardPaths: wrong permissions on runtime directory /config/.XDG, 0755 instead of 0700
krita | QStandardPaths: wrong permissions on runtime directory /config/.XDG, 0755 instead of 0700
krita | krita.lib.resources: Created table "version_information"
krita | krita.lib.resources: Created table "storage_types"
krita | krita.lib.resources: Created table "resource_types"
krita | krita.lib.resources: Created table "storages"
krita | krita.lib.resources: Created table "tags"
krita | krita.lib.resources: Created table "resources"
krita | krita.lib.resources: Created table "versioned_resources"
krita | krita.lib.resources: Created table "resource_tags"
krita | krita.lib.resources: Created table "metadata"
krita | krita.lib.resources: Created table "tags_storages"
krita | krita.lib.resources: Created table "tag_translations"
It’s not perfect, but more functional than the k8s hosted instance
If I want to save that output to use in something else, i can just save to the config directory
and pull it through the native file system mount to WSL
and when I’m done for now, I can use ctrl-c
in the shell to stop the docker compose service
and if i want it removed alltogether I can use docker compose down
builder@LuiGi:~/Workspaces/OSLocal/krita$ docker compose down
[+] Running 2/2
✔ Container krita Removed 0.1s
✔ Network krita_default Removed 0.3s
builder@LuiGi:~/Workspaces/OSLocal/krita$
Inkscape
This made me realize I could do the same setup for Linuxserver’s Inkscape
Just create the compose file
builder@LuiGi:~/Workspaces/OSLocal/inkscape$ mkdir config
builder@LuiGi:~/Workspaces/OSLocal/inkscape$ cat docker-compose.yaml
services:
inkscape:
image: lscr.io/linuxserver/inkscape:latest
container_name: inkscape
security_opt:
- seccomp:unconfined #optional
environment:
- PUID=1000
- PGID=1000
- TZ=America/Chicago
volumes:
- /home/builder/Workspaces/OSLocal/inkscape/config:/config
ports:
- 3000:3000
- 3001:3001
restart: unless-stopped
Then fire it up
builder@LuiGi:~/Workspaces/OSLocal/inkscape$ docker compose up
[+] Running 14/14
✔ inkscape Pulled 17.7s
✔ a5e3ea2009e2 Already exists 0.0s
✔ e1cde46db0e1 Already exists 0.0s
✔ 46f2d9bbcff7 Already exists 0.0s
✔ f2d94bf0c7ba Already exists 0.0s
✔ 0469477658fc Already exists 0.0s
✔ 35e63de34f77 Already exists 0.0s
✔ 681cde493f59 Already exists 0.0s
✔ 63ef96ffaedd Already exists 0.0s
✔ 1c6d24f55ad9 Already exists 0.0s
✔ 109b5f323c14 Already exists 0.0s
✔ fd6c3b0eb1fb Already exists 0.0s
✔ ff8a5133572d Pull complete 11.4s
✔ 31ede1d86330 Pull complete 11.4s
[+] Running 2/2
✔ Network inkscape_default Created 0.1s
✔ Container inkscape Created 0.3s
Attaching to inkscape
inkscape | [migrations] started
inkscape | [migrations] no migrations found
inkscape | ───────────────────────────────────────
inkscape |
inkscape | ██╗ ███████╗██╗ ██████╗
inkscape | ██║ ██╔════╝██║██╔═══██╗
inkscape | ██║ ███████╗██║██║ ██║
inkscape | ██║ ╚════██║██║██║ ██║
inkscape | ███████╗███████║██║╚██████╔╝
inkscape | ╚══════╝╚══════╝╚═╝ ╚═════╝
inkscape |
inkscape | Brought to you by linuxserver.io
inkscape | ───────────────────────────────────────
inkscape |
inkscape | To support LSIO projects visit:
inkscape | https://www.linuxserver.io/donate/
inkscape |
inkscape | ───────────────────────────────────────
inkscape | GID/UID
inkscape | ───────────────────────────────────────
inkscape |
inkscape | User UID: 1000
inkscape | User GID: 1000
inkscape | ───────────────────────────────────────
inkscape | ....+++++++++++++++++++++++++++++++++++++++*..+....+.........+..+......+....+........+...+...+++++++++++++++++++++++++++++++++++++++*...+..+...+....+...+......+............+..+...+....+.........+.....+.+.........+..+...+......+...+......+......+....+..+...............+.............+..+...+....+..+....+.........+...........+.......+............+...+..+.++++++
inkscape | .........+......+..+...+.......+...+..+....+...+..+......+...+..........+..+....+.....+.+...+...............+......+.....................+........+....+++++++++++++++++++++++++++++++++++++++*..+............+.+..+.......+.....+......+......+......+...+.+...+...........+....+...+............+.....+................+..+...+..........+++++++++++++++++++++++++++++++++++++++*.......+.........+.....+.+..+.+..+....+..................+........+...+......+......+.......+.....+...+....+...+.....+.......+...+...+...+...........+.+...+..+...+......+...+.............+...........................+........+....+..............+......+..................+...+...+....+.....+...+.......+...+...+..+...+................+.....+...+...+...+.........+.+.....+......+.+.........+.....+...+.......+.....+...+...+...+.+..................+.....+.+..............+...+....+......+..+...+............+.......+......+...+.........+..+.........+....+..+....+...........+....+..+.............+..+..........+........+.+...+......+.....+.......+.....+............++++++
inkscape | -----
inkscape | [custom-init] No custom files found, skipping...
inkscape | [ls.io-init] done.
inkscape | Openbox-Message: Failed to open the display from the DISPLAY environment variable.
inkscape | _XSERVTransmkdir: ERROR: euid != 0,directory /tmp/.X11-unix will not be created.
inkscape |
inkscape | Xvnc KasmVNC 1.3.3 - built Jun 28 2025 19:01:22
inkscape | Copyright (C) 1999-2018 KasmVNC Team and many others (see README.me)
inkscape | See http://kasmweb.com for information on KasmVNC.
inkscape | Underlying X server release 12101014
inkscape |
inkscape | [mi] mieq: warning: overriding existing handler 0 with 0x58be34f54a90 for event 2
inkscape | [mi] mieq: warning: overriding existing handler 0 with 0x58be34f54a90 for event 3
inkscape | 19
inkscape | Obt-Message: Xinerama extension is not present on the server
inkscape |
inkscape | ** (org.inkscape.Inkscape:299): WARNING **: 07:11:35.657: Can't open file: /config/.config/inkscape/cphistory.xml (doesn't exist)
inkscape |
inkscape | (org.inkscape.Inkscape:299): Gtk-CRITICAL **: 07:11:37.390: invalid accelerator string ''
inkscape |
inkscape | (org.inkscape.Inkscape:299): Gtk-CRITICAL **: 07:11:37.390: gtk_application_accels_get_actions_for_accel: assertion 'accel_key.key != 0' failed
inkscape |
inkscape | (org.inkscape.Inkscape:299): Gtk-WARNING **: 07:11:37.394: Unable to parse accelerator '': ignored request to install 1 accelerators
I’m not that good with Inkscape, but here you can see me whip up a image, save and view it
I’ve had bookmarked this InkScape tutorial I wanted to try and now I can.
This made me wonder if I should just try the windows binary
The Windows app does work, but i also had it crash a couple times on me so I would recommend saving often
Zipline
I had a note in Vikunja to circle back on Zipline which is an Open-Source containerized file server.
They have a docker compose file:
services:
postgresql:
image: postgres:16
restart: unless-stopped
env_file:
- .env
environment:
POSTGRES_USER: ${POSTGRESQL_USER:-zipline}
POSTGRES_PASSWORD: ${POSTGRESQL_PASSWORD:?POSTGRESSQL_PASSWORD is required}
POSTGRES_DB: ${POSTGRESQL_DB:-zipline}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'zipline']
interval: 10s
timeout: 5s
retries: 5
zipline:
image: ghcr.io/diced/zipline
ports:
- '3000:3000'
env_file:
- .env
environment:
- DATABASE_URL=postgres://${POSTGRESQL_USER:-zipline}:${POSTGRESQL_PASSWORD}@postgresql:5432/${POSTGRESQL_DB:-zipline}
depends_on:
postgresql:
condition: service_healthy
volumes:
- './uploads:/zipline/uploads'
- './public:/zipline/public'
- './themes:/zipline/themes'
healthcheck:
test: ['CMD', 'wget', '-q', '--spider', 'http://localhost:3000/api/healthcheck']
interval: 15s
timeout: 2s
retries: 2
volumes:
pgdata:
Let’s start with Docker and move from there.
builder@bosgamerz9:~/zipline$ curl -LO https://zipline.diced.sh/docker-compose.yml
% Total % Received % Xferd Average Speed Time Time Time Current
Dload Upload Total Spent Left Speed
100 15 0 15 0 0 47 0 --:--:-- --:--:-- --:--:-- 47
100 1117 0 1117 0 0 452 0 --:--:-- 0:00:02 --:--:-- 970
builder@bosgamerz9:~/zipline$ cat docker-compose.yml
services:
postgresql:
image: postgres:16
restart: unless-stopped
env_file:
- .env
environment:
POSTGRES_USER: ${POSTGRESQL_USER:-zipline}
POSTGRES_PASSWORD: ${POSTGRESQL_PASSWORD:?POSTGRESSQL_PASSWORD is required}
POSTGRES_DB: ${POSTGRESQL_DB:-zipline}
volumes:
- pgdata:/var/lib/postgresql/data
healthcheck:
test: ['CMD', 'pg_isready', '-U', 'zipline']
interval: 10s
timeout: 5s
retries: 5
zipline:
image: ghcr.io/diced/zipline:latest
restart: unless-stopped
ports:
- '3000:3000'
env_file:
- .env
environment:
- DATABASE_URL=postgres://${POSTGRESQL_USER:-zipline}:${POSTGRESQL_PASSWORD}@postgresql:5432/${POSTGRESQL_DB:-zipline}
depends_on:
postgresql:
condition: service_healthy
volumes:
- './uploads:/zipline/uploads'
- './public:/zipline/public'
- './themes:/zipline/themes'
healthcheck:
test: ['CMD', 'wget', '-q', '--spider', 'http://0.0.0.0:3000/api/healthcheck']
interval: 15s
timeout: 2s
retries: 2
volumes:
pgdata:
We can create an .env file with some random secrets
builder@bosgamerz9:~/zipline$ echo "POSTGRESQL_PASSWORD=$(openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 | tr -d '\n')" > .env
echo "CORE_SECRET=$(openssl rand -base64 42 | tr -dc A-Za-z0-9 | cut -c -32 | tr -d '\n')" >> .env
builder@bosgamerz9:~/zipline$ cat .env
POSTGRESQL_PASSWORD=wBxxxxxxxxxxxxxxxxxxxV
CORE_SECRET=Jxxxxxxxxxxxxxxxxxd
I can fire up the containers now
builder@bosgamerz9:~/zipline$ sudo docker compose up -d
[+] Running 34/34
✔ zipline Pulled 29.9s
✔ f18232174bc9 Already exists 0.0s
✔ 5056ee185863 Pull complete 1.9s
✔ 974362762896 Pull complete 1.9s
✔ 18d8590713a4 Pull complete 2.0s
✔ 384c3be0b1f5 Pull complete 2.0s
✔ 0e6f14bf1b1c Pull complete 2.4s
✔ 44bcba074ba0 Pull complete 2.4s
✔ e97cf444ba59 Pull complete 2.4s
✔ fc520afb2f60 Pull complete 2.4s
✔ ca56b87feb09 Pull complete 2.5s
✔ 315d2a42fb2c Pull complete 27.9s
✔ 608a0238234e Pull complete 28.0s
✔ 36ad4b4ebc78 Pull complete 28.5s
✔ 8fb31568ddc7 Pull complete 28.5s
✔ 6da86230782a Pull complete 28.5s
✔ 6a47961ac2b6 Pull complete 28.6s
✔ d1298c38b91d Pull complete 29.0s
✔ 8341e0163493 Pull complete 29.0s
✔ postgresql Pulled 10.8s
✔ 3da95a905ed5 Pull complete 4.8s
✔ 525b0b7a1d26 Pull complete 4.8s
✔ bfee09e98282 Pull complete 4.9s
✔ c3f120721a2e Pull complete 5.2s
✔ 0f750f482557 Pull complete 5.8s
✔ fc5e0f223019 Pull complete 6.1s
✔ 94f4e8dd553a Pull complete 6.3s
✔ b5735d961ce0 Pull complete 6.6s
✔ ee4088773a9e Pull complete 9.8s
✔ 3f1ebdb897f5 Pull complete 9.8s
✔ 10a344c4eea9 Pull complete 9.8s
✔ 33fda3db53ca Pull complete 9.8s
✔ 4f5214727328 Pull complete 9.8s
✔ 73e2aac8af8b Pull complete 9.8s
[+] Running 4/4
✔ Network zipline_default Created 0.0s
✔ Volume "zipline_pgdata" Created 0.0s
✔ Container zipline-postgresql-1 Healthy 11.0s
✔ Container zipline-zipline-1 Started 10.8s
As I’m remote, I’ll want to create a proper HTTPS ingress to reach it
First, I create an A 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 zipline
{
"ARecords": [
{
"ipv4Address": "75.73.224.240"
}
],
"TTL": 3600,
"etag": "9c8ab014-9c0a-4612-9e60-8635514c5733",
"fqdn": "zipline.tpk.pw.",
"id": "/subscriptions/d955c0ba-13dc-44cf-a29a-8fed74cbb22d/resourceGroups/idjdnsrg/providers/Microsoft.Network/dnszones/tpk.pw/A/zipline",
"name": "zipline",
"provisioningState": "Succeeded",
"resourceGroup": "idjdnsrg",
"targetResource": {},
"trafficManagementProfile": {},
"type": "Microsoft.Network/dnszones/A"
}
Then a server, endpoint and ingress
$ cat ingress.zipline.yaml
---
apiVersion: v1
kind: Endpoints
metadata:
name: zipline-external-ip
subsets:
- addresses:
- ip: 192.168.1.143
ports:
- name: zipline
port: 3000
protocol: TCP
---
apiVersion: v1
kind: Service
metadata:
name: zipline-external-ip
spec:
clusterIP: None
clusterIPs:
- None
internalTrafficPolicy: Cluster
ipFamilies:
- IPv4
- IPv6
ipFamilyPolicy: RequireDualStack
ports:
- name: zipline
port: 80
protocol: TCP
targetPort: 3000
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/ssl-redirect: "true"
nginx.ingress.kubernetes.io/proxy-body-size: "0"
nginx.ingress.kubernetes.io/proxy-read-timeout: "3600"
nginx.ingress.kubernetes.io/proxy-send-timeout: "3600"
nginx.org/client-max-body-size: "0"
nginx.org/proxy-connect-timeout: "3600"
nginx.org/proxy-read-timeout: "3600"
nginx.org/websocket-services: zipline-external-ip
name: zipline-ingress
spec:
rules:
- host: zipline.tpk.pw
http:
paths:
- backend:
service:
name: zipline-external-ip
port:
number: 80
path: /
pathType: Prefix
tls:
- hosts:
- zipline.tpk.pw
secretName: zipline-tls
status:
loadBalancer: {}
Then apply
$ kubectl apply -f ./ingress.zipline.yaml
endpoints/zipline-external-ip created
service/zipline-external-ip created
Warning: annotation "kubernetes.io/ingress.class" is deprecated, please use 'spec.ingressClassName' instead
ingress.networking.k8s.io/zipline-ingress created
As soon as I saw the cert was valid
$ kubectl get cert zipline-tls
NAME READY SECRET AGE
zipline-tls True zipline-tls 87s
I tried the web app
I can now make a super user account
The last step is to click finish
Here I’ll try and upload a Word doc to start
It replies with a link
It did create an anonymous link that worked, albeit renamed the file
Now, I can go to the files and edit the details to set a password
When i try the URL now, it does require a password
If I delete the file, then come back, it shows a 404
In administration, we can make sure our URLs are HTTPS and with the right URL
Now when I upload I see “https” instead of “http”
We can also do things like expiring and number of views
And verified it viewed once then closed. I can say that PDFs show in the browser (but Docx files do download).
Summary
Today we looked at a couple of nice image creation tools, Krita and Inkscape as served up by Linuxserver. While they worked, I did find the latency didn’t make them all that useful. The containerized instances, however, worked nearly as good as the fat clients in windows.
We also looked at Zipline which is a nice small open-source containerized file serving app.