Docker Webapp Summary


To streamline the development of new, quick, low-overhead Flask applications, I created a lightweight tool to monitor active Docker containers. The goal was to enable rapid iteration on local containers during development. Since this was internal infrastructure, I focused on building a solution that prioritized functionality and simplicity over aesthetics.

This post outlines the application’s functionality and shares the core code for anyone looking to replicate or adapt it.

The app provides a quick way to view all running Docker instances on a machine and includes direct links to access them. Built with Flask and some generated code, it’s designed to be flexible and easy to update. A single command generates the necessary data, which can then be pasted directly into place.

Basic architecture

It’s nothing special, just some quick code generated to fill the purpose, but nice to get running in an hour. Maybe it could be useful to you!

Python
from flask import Flask, request, jsonify, render_template
import os
import json

# Get the absolute path to the templates folder
BASE_DIR = os.path.dirname(os.path.abspath(__file__))
TEMPLATES_DIR = os.path.join(BASE_DIR, "../templates")

app = Flask(__name__, template_folder=TEMPLATES_DIR)

# File path for storing the JSON data
STORED_JSON_PATH = "containers.json"

# Route for home page
def generate_url(hostname, port, container_name):
    if hostname:
        return f"https://{hostname}"
    return f"http://localhost:{port}"

@app.route("/", methods=["GET", "POST"])
def home():
    command_example = "docker ps --format '{{json .}}'"
    containers = []
    if request.method == "POST":
        try:
            json_data = request.form.get("json_data")

            # Parse and validate JSON
            lines = json_data.strip().split("\n")
            data = [json.loads(line) for line in lines]

            for container in data:
                labels = container.get("Labels", "")
                hostname = None
                if labels:
                    # Extract custom.hostname if available
                    label_dict = dict(label.split("=") for label in labels.split(",") if "=" in label)
                    hostname = label_dict.get("custom.hostname")

                ports = container.get("Ports", "").split(",")[0]  # Use the first port entry
                if "->" in ports:
                    port = ports.split("->")[0].split(":")[-1]
                else:
                    port = None

                name = container.get("Names", "").split(",")[0]

                if name:
                    containers.append({
                        "url": generate_url(hostname, port, name),
                        "name": name
                    })

            # Save the JSON data to a file
            with open(STORED_JSON_PATH, "w") as file:
                json.dump(data, file, indent=4)

        except json.JSONDecodeError:
            return render_template("index.html", error="Invalid JSON format.", command_example=command_example, containers=[]), 400

    elif os.path.exists(STORED_JSON_PATH):
        # Load the stored JSON
        with open(STORED_JSON_PATH, "r") as file:
            data = json.load(file)
            for container in data:
                labels = container.get("Labels", "")
                hostname = None
                if labels:
                    label_dict = dict(label.split("=") for label in labels.split(",") if "=" in label)
                    hostname = label_dict.get("custom.hostname")

                ports = container.get("Ports", "").split(",")[0]
                if "->" in ports:
                    port = ports.split("->")[0].split(":")[-1]
                else:
                    port = None

                name = container.get("Names", "").split(",")[0]

                if name:
                    containers.append({
                        "url": generate_url(hostname, port, name),
                        "name": name
                    })

    return render_template("index.html", command_example=command_example, containers=containers)

if __name__ == "__main__":
    app.run(debug=True, host="0.0.0.0")

HTML
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Apps Home</title>
    <link href="https://fonts.googleapis.com/icon?family=Material+Icons" rel="stylesheet">
    <link href="https://cdn.jsdelivr.net/npm/@material/web@latest/dist/material-components-web.min.css" rel="stylesheet">
    <style>
        body {
            font-family: Roboto, Arial, sans-serif;
            margin: 0;
            padding: 0;
            display: flex;
            flex-direction: column;
            align-items: center;
            justify-content: center;
            background-color: #f5f5f5;
            min-height: 100vh;
        }
        .container {
            width: 80%;
            max-width: 600px;
            margin-top: 2rem;
            padding: 1.5rem;
            background: #ffffff;
            border-radius: 8px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
        }
        .title {
            text-align: center;
            font-size: 1.5rem;
            margin-bottom: 1rem;
        }
        ul {
            list-style: none;
            padding: 0;
        }
        li {
            margin: 0.5rem 0;
        }
        a {
            text-decoration: none;
            color: #6200ee;
        }
        a:hover {
            text-decoration: underline;
        }
        .error {
            color: red;
            margin-bottom: 1rem;
        }
        .command-example {
            background: #e0f7fa;
            padding: 1rem;
            border-radius: 8px;
            font-family: monospace;
            margin-bottom: 1rem;
            position: relative;
        }
        .command-example button {
            position: absolute;
            top: 10px;
            right: 10px;
            background: #6200ee;
            color: white;
            border: none;
            border-radius: 4px;
            padding: 5px 10px;
            cursor: pointer;
        }
        .command-example button:hover {
            background: #3700b3;
        }
        textarea {
            width: 100%;
            height: 150px;
            padding: 10px;
            margin-bottom: 1rem;
            font-family: monospace;
            font-size: 1rem;
        }
        button {
            padding: 10px 20px;
            background: #6200ee;
            color: white;
            border: none;
            border-radius: 4px;
            cursor: pointer;
            font-size: 1rem;
        }
        button:hover {
            background: #3700b3;
        }
    </style>
    <script>
        function copyCommand() {
            const command = document.getElementById("command-example-text").innerText;
            navigator.clipboard.writeText(command).then(() => {
                alert("Command copied to clipboard!");
            }).catch(err => {
                console.error("Failed to copy command: ", err);
            });
        }
    </script>
</head>
<body>
    <div class="container">
        <h1 class="title">Apps Home</h1>
        {% if error %}
        <p class="error">{{ error }}</p>
        {% endif %}

        <h2>Configured Apps</h2>
        <ul>
            {% for container in containers %}
            <li><a href="{{ container.url }}" target="_blank">{{ container.name }}</a> ({{ container.url }})</li>
            {% endfor %}
        </ul>

        <h2>Update Configured Apps</h2>
        <div class="command-example">
            <strong>Command Example:</strong>
            <code id="command-example-text">{% raw %}docker ps --format '{{json .}}' | xclip -selection clipboard{% endraw %}</code>
            <button onclick="copyCommand()">Copy</button>
        </div>
        <form action="/" method="post">
            <label for="json_data">Enter Updated JSON (Newline-Separated):</label>
            <textarea id="json_data" name="json_data" placeholder="Paste your JSON here..." required></textarea>
            <button type="submit">Submit</button>
        </form>
    </div>
</body>
</html>