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:
EnvironmentFile=/etc/environmentin your.servicefilesystemctl enablein 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.