{"id":49078,"date":"2025-04-11T13:37:04","date_gmt":"2025-04-11T13:37:04","guid":{"rendered":"https:\/\/www.devopsschool.com\/blog\/?p=49078"},"modified":"2025-07-12T05:38:39","modified_gmt":"2025-07-12T05:38:39","slug":"production-ready-setup-apache-laravel-docker-dynamic-reverse-proxy-for-multiple-users","status":"publish","type":"post","link":"https:\/\/www.devopsschool.com\/blog\/production-ready-setup-apache-laravel-docker-dynamic-reverse-proxy-for-multiple-users\/","title":{"rendered":"Production-Ready Setup: Apache + Laravel + Docker + Dynamic Reverse Proxy for Multiple Users"},"content":{"rendered":"\n<p>This updated guide explains how to implement a <strong>scalable, production-ready setup<\/strong> where each user gets a Docker container and unique subdomain like <code>user123.wizbrand.com<\/code>, without creating new Apache VirtualHosts for each.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 1. Apache Configuration (Dynamic Reverse Proxy)<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Apache Modules (Enable Once)<\/h3>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">sudo a2enmod proxy\nsudo a2enmod proxy_http\nsudo a2enmod rewrite\nsudo systemctl restart apache2\n<\/code><\/span><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Apache Wildcard VirtualHost (Single File)<\/h3>\n\n\n\n<p>Edit <code>\/opt\/lampp\/etc\/extra\/httpd-vhosts.conf<\/code> or <code>\/etc\/apache2\/sites-available\/000-default.conf<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-1\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">&lt;VirtualHost *:<span class=\"hljs-number\">80<\/span>&gt;\n    ServerName wizbrand.com\n    ServerAlias *.wizbrand.com\n\n    ProxyPreserveHost On\n    RewriteEngine On\n\n    <span class=\"hljs-comment\"># Extract user ID from subdomain (e.g., user123)<\/span>\n    RewriteCond %{HTTP_HOST} ^user(&#91;<span class=\"hljs-number\">0<\/span><span class=\"hljs-number\">-9<\/span>]+)\\.wizbrand\\.com$ &#91;NC]\n    RewriteRule ^\/(.*)$ http:<span class=\"hljs-comment\">\/\/127.0.0.1:9%1\/$1 &#91;P,L]<\/span>\n\n    ProxyPassReverse \/ http:<span class=\"hljs-comment\">\/\/127.0.0.1\/<\/span>\n&lt;\/VirtualHost&gt;\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-1\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Restart Apache:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">sudo systemctl restart apache2\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 2. Laravel Controller to Spawn Container and Return URL<\/h2>\n\n\n\n<p>Create <code>ContainerController.php<\/code>:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-2\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-keyword\">namespace<\/span> <span class=\"hljs-title\">App<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Controllers<\/span>;\n\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Http<\/span>\\<span class=\"hljs-title\">Request<\/span>;\n<span class=\"hljs-keyword\">use<\/span> <span class=\"hljs-title\">Illuminate<\/span>\\<span class=\"hljs-title\">Support<\/span>\\<span class=\"hljs-title\">Facades<\/span>\\<span class=\"hljs-title\">Auth<\/span>;\n\n<span class=\"hljs-class\"><span class=\"hljs-keyword\">class<\/span> <span class=\"hljs-title\">ContainerController<\/span> <span class=\"hljs-keyword\">extends<\/span> <span class=\"hljs-title\">Controller<\/span>\n<\/span>{\n    <span class=\"hljs-keyword\">public<\/span> <span class=\"hljs-function\"><span class=\"hljs-keyword\">function<\/span> <span class=\"hljs-title\">createUserContainer<\/span><span class=\"hljs-params\">(Request $request)<\/span>\n    <\/span>{\n        $user = Auth::user();\n        $containerName = <span class=\"hljs-string\">'user-'<\/span> . $user-&gt;id;\n        $port = <span class=\"hljs-number\">9000<\/span> + $user-&gt;id;\n\n        <span class=\"hljs-comment\">\/\/ Start Docker container mapped to unique port<\/span>\n        shell_exec(<span class=\"hljs-string\">\"docker run -d -p {$port}:80 --name {$containerName} my-image\"<\/span>);\n\n        <span class=\"hljs-comment\">\/\/ Return container's unique URL<\/span>\n        <span class=\"hljs-keyword\">return<\/span> response()-&gt;json(&#91;\n            <span class=\"hljs-string\">'url'<\/span> =&gt; <span class=\"hljs-string\">\"http:\/\/user{$user-&gt;id}.wizbrand.com\"<\/span>\n        ]);\n    }\n}\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-2\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<h3 class=\"wp-block-heading\">Add Laravel Route (<code>routes\/web.php<\/code>)<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-3\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\">Route::get(<span class=\"hljs-string\">'\/create-container'<\/span>, &#91;ContainerController::class, <span class=\"hljs-string\">'createUserContainer'<\/span>])-&gt;middleware(<span class=\"hljs-string\">'auth'<\/span>);\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-3\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 3. Domain Setup (Production)<\/h2>\n\n\n\n<p>In your domain provider&#8217;s DNS (e.g., GoDaddy, Cloudflare), add:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Type<\/th><th>Name<\/th><th>Value<\/th><\/tr><\/thead><tbody><tr><td>A<\/td><td><code>*<\/code><\/td><td><code>your_server_ip<\/code><\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>This wildcard allows <code>user123.wizbrand.com<\/code> \u2192 your server.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 4. Local Testing Setup<\/h2>\n\n\n\n<h3 class=\"wp-block-heading\">Add to <code>\/etc\/hosts<\/code> (on dev machine)<\/h3>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-4\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\">127<span class=\"hljs-selector-class\">.0<\/span><span class=\"hljs-selector-class\">.0<\/span><span class=\"hljs-selector-class\">.1<\/span> <span class=\"hljs-selector-tag\">user123<\/span><span class=\"hljs-selector-class\">.wizbrand<\/span><span class=\"hljs-selector-class\">.com<\/span>\n127<span class=\"hljs-selector-class\">.0<\/span><span class=\"hljs-selector-class\">.0<\/span><span class=\"hljs-selector-class\">.1<\/span> <span class=\"hljs-selector-tag\">user456<\/span><span class=\"hljs-selector-class\">.wizbrand<\/span><span class=\"hljs-selector-class\">.com<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-4\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Or use:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><code>nip.io<\/code>: <code>user123.127.0.0.1.nip.io<\/code><\/li>\n\n\n\n<li><code>sslip.io<\/code>: <code>user123.127.0.0.1.sslip.io<\/code><\/li>\n<\/ul>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 5. Docker Image Assumptions<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Exposes port <code>80<\/code><\/li>\n\n\n\n<li>Named <code>my-image<\/code> or configured dynamically<\/li>\n<\/ul>\n\n\n\n<p>Example container:<\/p>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-5\" data-shcb-language-name=\"CSS\" data-shcb-language-slug=\"css\"><span><code class=\"hljs language-css\"><span class=\"hljs-selector-tag\">docker<\/span> <span class=\"hljs-selector-tag\">run<\/span> <span class=\"hljs-selector-tag\">-d<\/span> <span class=\"hljs-selector-tag\">-p<\/span> 9123<span class=\"hljs-selector-pseudo\">:80<\/span> <span class=\"hljs-selector-tag\">--name<\/span> <span class=\"hljs-selector-tag\">user-123<\/span> <span class=\"hljs-selector-tag\">my-image<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-5\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">CSS<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">css<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2705 6. Test Script (Optional Bash)<\/h2>\n\n\n<pre class=\"wp-block-code\" aria-describedby=\"shcb-language-6\" data-shcb-language-name=\"PHP\" data-shcb-language-slug=\"php\"><span><code class=\"hljs language-php\"><span class=\"hljs-comment\">#!\/bin\/bash<\/span>\nUSER_ID=$<span class=\"hljs-number\">1<\/span>\nPORT=$((<span class=\"hljs-number\">9000<\/span> + USER_ID))\nCONTAINER=<span class=\"hljs-string\">\"user-${USER_ID}\"<\/span>\n\n<span class=\"hljs-comment\"># Launch container<\/span>\nsudo docker run -d -p ${PORT}:<span class=\"hljs-number\">80<\/span> --name ${CONTAINER} my-image\n\n<span class=\"hljs-comment\"># Access URL<\/span>\n<span class=\"hljs-keyword\">echo<\/span> <span class=\"hljs-string\">\"Container for user ${USER_ID} available at: http:\/\/user${USER_ID}.wizbrand.com\"<\/span>\n<\/code><\/span><small class=\"shcb-language\" id=\"shcb-language-6\"><span class=\"shcb-language__label\">Code language:<\/span> <span class=\"shcb-language__name\">PHP<\/span> <span class=\"shcb-language__paren\">(<\/span><span class=\"shcb-language__slug\">php<\/span><span class=\"shcb-language__paren\">)<\/span><\/small><\/pre>\n\n\n<p>Usage:<\/p>\n\n\n<pre class=\"wp-block-code\"><span><code class=\"hljs\">.\/create-container.sh 123\n<\/code><\/span><\/pre>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde0 Summary<\/h2>\n\n\n\n<figure class=\"wp-block-table\"><table class=\"has-fixed-layout\"><thead><tr><th>Component<\/th><th>Role<\/th><\/tr><\/thead><tbody><tr><td>Apache (Wildcard)<\/td><td>Dynamic proxy via subdomain-to-port<\/td><\/tr><tr><td>Laravel<\/td><td>Container creator &amp; subdomain router<\/td><\/tr><tr><td>Docker<\/td><td>User-specific environments<\/td><\/tr><tr><td>DNS (Wildcard)<\/td><td>Resolves all subdomains to one IP<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>This approach is clean, scalable, reload-free, and requires <strong>just one Apache config<\/strong> regardless of user count. Let me know if you&#8217;d like to add Let&#8217;s Encrypt SSL automation, container auto-cleanup, or database tracking next.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>This updated guide explains how to implement a scalable, production-ready setup where each user gets a Docker container and unique subdomain like user123.wizbrand.com, without creating new Apache VirtualHosts for each&#8230;. <\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"","sticky":false,"template":"","format":"standard","meta":{"_joinchat":[],"footnotes":""},"categories":[4862],"tags":[],"class_list":["post-49078","post","type-post","status-publish","format-standard","hentry","category-docker"],"_links":{"self":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49078","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/comments?post=49078"}],"version-history":[{"count":1,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49078\/revisions"}],"predecessor-version":[{"id":49079,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/posts\/49078\/revisions\/49079"}],"wp:attachment":[{"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/media?parent=49078"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/categories?post=49078"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/www.devopsschool.com\/blog\/wp-json\/wp\/v2\/tags?post=49078"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}