{"id":8,"date":"2025-07-29T09:45:20","date_gmt":"2025-07-29T09:45:20","guid":{"rendered":"https:\/\/wallaceat.me\/?p=8"},"modified":"2025-11-24T22:50:39","modified_gmt":"2025-11-24T22:50:39","slug":"hosting-immich-locally-with-docker-and-caddy","status":"publish","type":"post","link":"https:\/\/wallaceat.me\/?p=8","title":{"rendered":"Hosting Immich Locally with Docker and Caddy"},"content":{"rendered":"<p>In this post, I document the steps I followed to set up <strong>Immich<\/strong> \u2013 a self-hosted photo and video backup solution \u2013 on my local server using <strong>Docker<\/strong> and <strong>Caddy<\/strong>. I also configured it to be accessible over HTTPS with a custom subdomain.<\/p>\n<h2>\ud83d\udd27 Requirements<\/h2>\n<ul>\n<li>Docker and Docker Compose installed<\/li>\n<li>A registered domain (e.g. wallaceat.me)<\/li>\n<li>Caddy running in a container for reverse proxy and HTTPS<\/li>\n<\/ul>\n<h2>\ud83d\udcc1 Directory Structure<\/h2>\n<pre><code>\/home\/wallace\/server\/immich\n\u251c\u2500\u2500 docker-compose.yml\n\u251c\u2500\u2500 stack.env\n\u2514\u2500\u2500 ...<\/code><\/pre>\n<h2>\ud83d\udcdd docker-compose.yml<\/h2>\n<p>Here&#8217;s the Docker Compose file I used:<\/p>\n<pre><code>version: \"3.8\"\nservices:\n  immich-server:\n    container_name: immich_server\n    image: ghcr.io\/immich-app\/immich-server:release\n    volumes:\n      - \/mnt\/immich:\/usr\/src\/app\/upload:rw\n      - \/etc\/localtime:\/etc\/localtime:ro\n    env_file:\n      - stack.env\n    ports:\n      - '2283:2283'\n    depends_on:\n      - redis\n      - database\n    restart: always\n    networks:\n      - immich-network\n\n  immich-machine-learning:\n    image: ghcr.io\/immich-app\/immich-machine-learning:release\n    volumes:\n      - model-cache:\/cache\n    env_file:\n      - stack.env\n    restart: always\n    networks:\n      - immich-network\n\n  redis:\n    image: redis:6.2-alpine\n    restart: always\n    networks:\n      - local\n\n  database:\n    image: tensorchord\/pgvecto-rs:pg14-v0.2.0\n    environment:\n      POSTGRES_PASSWORD: your_password\n      POSTGRES_USER: your_user\n      POSTGRES_DB: your_db\n    volumes:\n      - \/mnt\/postgres:\/var\/lib\/postgresql\/data\n    restart: always\n    networks:\n      - local\n\nvolumes:\n  model-cache:\n\nnetworks:\n  local<\/code><\/pre>\n<h2>\ud83c\udf10 Caddy Configuration<\/h2>\n<p>My Caddy server is already running in a separate container and serves multiple services. Here&#8217;s the config snippet I added to the Caddyfile to expose Immich:<\/p>\n<pre><code>photos.wallaceat.me {\n  reverse_proxy immich_server:2283\n}<\/code><\/pre>\n<p>If using an external Caddy container, ensure it&#8217;s in the same Docker network or resolve to the host&#8217;s IP and port.<\/p>\n<h2>\ud83c\udf0d Domain and HTTPS<\/h2>\n<p>I pointed the subdomain <code>subdomain.wallaceat.me<\/code> to my server&#8217;s IP using an <strong>A record<\/strong> on my DNS provider (Dynu). Caddy automatically issued a Let&#8217;s Encrypt certificate.<\/p>\n<h2>\ud83d\udee0 Common Issues<\/h2>\n<ul>\n<li><strong>Port conflicts<\/strong>: Ensure no other services are using port 80 or 443<\/li>\n<li><strong>Redis timeout<\/strong>: Check if the Redis container is running and available on the Docker network<\/li>\n<li><strong>Permissions<\/strong>: Mount points like \/mnt\/immich must be accessible by Docker<\/li>\n<\/ul>\n<h2>\ud83d\udccc Tips<\/h2>\n<ul>\n<li>Automate DNS updates using Dynu\u2019s dynamic IP update client if your IP is dynamic<\/li>\n<li>Consider automating deployment via GitHub Actions or Drone CI in the future<\/li>\n<\/ul>\n","protected":false},"excerpt":{"rendered":"<p>In this post, I document the steps I followed to set up Immich \u2013 a self-hosted photo and video backup solution \u2013 on my local server using Docker and Caddy. I also configured it to be accessible over HTTPS with a custom subdomain. \ud83d\udd27 Requirements Docker and Docker Compose installed A registered domain (e.g. wallaceat.me) [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[],"class_list":["post-8","post","type-post","status-publish","format-standard","hentry","category-uncategorized"],"_links":{"self":[{"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/posts\/8","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/wallaceat.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=8"}],"version-history":[{"count":5,"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/posts\/8\/revisions"}],"predecessor-version":[{"id":31,"href":"https:\/\/wallaceat.me\/index.php?rest_route=\/wp\/v2\/posts\/8\/revisions\/31"}],"wp:attachment":[{"href":"https:\/\/wallaceat.me\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=8"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/wallaceat.me\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=8"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/wallaceat.me\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=8"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}