Skip to content

Building Custom Templates

Create custom Docker images based on the standard images (Debian 13 or NodeJS 24) that use environment variables set at container creation time.

How It Works

The standard images run systemd as PID 1 (system containers). This means is you need a systemd service to run your application. A custom oneshot service is present in the provided base images that writes container environment variables to /etc/environment and your service will need to explicitly read variables from that file. Below is a complete example on how to use this.

Example: Python Flask App

Directory structure:

my-flask-app/
├── Dockerfile
├── app.py
└── myapp.service

app.py:

#!/usr/bin/env python3
import os
from flask import Flask

app = Flask(__name__)
PORT = int(os.getenv('PORT', '8080'))
DATABASE_URL = os.getenv('DATABASE_URL', 'sqlite:///default.db')

@app.route('/')
def hello():
    return f"<h1>Hello from Flask!</h1><p>Port: {PORT}</p>"

if __name__ == '__main__':
    app.run(host='0.0.0.0', port=PORT)

myapp.service:

[Unit]
Description=My Flask Application
After=network.target

[Service]
Type=simple
User=www-data
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/environment
ExecStart=/usr/bin/python3 /opt/myapp/app.py
Restart=always

[Install]
WantedBy=multi-user.target

Dockerfile:

FROM ghcr.io/mieweb/opensource-server/base:latest

RUN apt-get update && \
    apt-get install -y python3 python3-pip python3-flask && \
    apt-get clean && rm -rf /var/lib/apt/lists/*

RUN mkdir -p /opt/myapp && chown www-data:www-data /opt/myapp
COPY app.py /opt/myapp/app.py
RUN chmod +x /opt/myapp/app.py

COPY myapp.service /etc/systemd/system/myapp.service
RUN systemctl enable myapp.service

EXPOSE 8080
LABEL org.mieweb.opensource-server.services.http.default-port="8080"

Build, push, then in the web UI select "Custom Docker Image" and enter your registry path. Add environment variables (PORT, DATABASE_URL, etc.) — they'll be written to /etc/environment and loaded by your service.

The same pattern applies to Node.js (use ghcr.io/mieweb/opensource-server/nodejs:latest base, ExecStart=/usr/bin/node) and Go (copy a static binary, use ghcr.io/mieweb/opensource-server/base:latest). The key elements are always:

  1. EnvironmentFile=/etc/environment in your .service file
  2. systemctl enable in your Dockerfile

Service Labels

Docker images can declare HTTP services via OCI labels under the org.mieweb.opensource-server.services.http namespace. When a user selects the image as a template, the container creation form auto-populates services from these labels.

Default Port

LABEL org.mieweb.opensource-server.services.http.default-port=3000

Creates one HTTP service on port 3000 with the container hostname as the external hostname.

Named Services

Named labels use the pattern ...http.<name>.<field> where <name> groups fields for a single service:

Label Required Description
...http.<name>.port Yes Internal port number
...http.<name>.hostnameSuffix No External hostname becomes <container-hostname>-<suffix>
...http.<name>.requireAuth No true, 1, or yes to enable authentication

Warning

The <name> groups fields together — a typo in the name (e.g., ozwell-studio vs ozwell-sutdio) causes fields to be treated as separate services.

# Main app on port 3000 (hostname: myapp.example.com)
LABEL org.mieweb.opensource-server.services.http.default-port=3000

# API on port 8080 (hostname: myapp-api.example.com, auth required)
LABEL org.mieweb.opensource-server.services.http.api.port=8080
LABEL org.mieweb.opensource-server.services.http.api.hostnameSuffix=api
LABEL org.mieweb.opensource-server.services.http.api.requireAuth=true

# Docs on port 9090 (hostname: myapp-docs.example.com)
LABEL org.mieweb.opensource-server.services.http.docs.port=9090
LABEL org.mieweb.opensource-server.services.http.docs.hostnameSuffix=docs

TCP ports declared by named services are excluded from the auto-populated transport services list (same dedup as default-port).

Testing

# Local
docker run -it -e PORT=8080 -e API_KEY=test ghcr.io/myorg/my-app:latest
docker exec <id> systemctl status myapp.service
docker exec <id> journalctl -u myapp.service -f

# In production (SSH into container)
cat /etc/environment
systemctl status myapp.service
journalctl -u myapp.service -n 100

Updating Variables

Temporary: SSH in, edit /etc/environment, run systemctl restart myapp.service. Lost on recreate.

Permanent: Edit container in the web UI and save — container is recreated with new variables.