--- title: "Website" date: 2025-04-07 description: "An overview of what I am using to host my digital content." image: url: "showcase.avif" alt: "Website showcase" categories: ["personal"] tags: ["self-host", "forgejo", "docker", "vps"] draft: true --- This post will outline my workflow of using a self-hosted Forgejo instance and Actions runner to automatically deploy my personal site and any software releases, all without having to rely on another provider. ![Website showcase](showcase.avif) ## Steps ### Private image access login? ```sh echo '' | docker login code.troylusty.com -u troy --password-stdin ``` ```sh echo $(htpasswd -nB user) | sed -e s/\\$/\\$\\$/g ``` ### Aliases for updating VPS and pruning Docker ```sh echo 'alias dockerclean="docker system prune -a --volumes"' >> .bashrc echo 'alias updateall="sudo apt update && sudo apt upgrade && sudo apt autoremove"' >> .bashrc ``` Thanks to [Tech Tales](https://tech-tales.blog/posts/2025/01-forgejo-runner-update) for the clean instructions on how to setup an Actions runner with Forgejo. ```sh docker compose run --rm forgejo-runner 'forgejo-runner' 'generate-config' > forgejo-runner/config.yml ``` Setup `container.docker_host: "unix:///var/run/docker.sock"` and `container.network: "forgejo"` and any labels such as `runner.labels: ["ubuntu-latest:docker://gitea/runner-images:ubuntu-latest"]` ``` docker compose run --rm -it forgejo-runner 'forgejo-runner' 'register' ``` Input `http://forgejo:3000` as the domain since forgejo is the container name in Docker and port 3000 is its relevant port. ## Docker compose ```yaml services: traefik: image: traefik:latest container_name: traefik command: - "--providers.docker" - "--providers.docker.exposedbydefault=false" - "--entryPoints.websecure.address=:443" - "--certificatesresolvers.myresolver.acme.tlschallenge=true" - "--certificatesresolvers.myresolver.acme.email=traefik@troylusty.com" - "--certificatesresolvers.myresolver.acme.storage=/letsencrypt/acme.json" - "--entrypoints.web.address=:80" - "--entrypoints.web.http.redirections.entrypoint.to=websecure" - "--entrypoints.web.http.redirections.entrypoint.scheme=https" - "--ping=true" labels: - "traefik.enable=true" - "com.centurylinklabs.watchtower.enable=true" - "traefik.http.middlewares.securityHeaders.headers.stsSeconds=31536000" - "traefik.http.middlewares.securityHeaders.headers.stsIncludeSubdomains=true" - "traefik.http.middlewares.securityHeaders.headers.frameDeny=true" - "traefik.http.middlewares.securityHeaders.headers.contentTypeNosniff=true" - "traefik.http.middlewares.securityHeaders.headers.contentSecurityPolicy=default-src 'self'; script-src 'self' 'unsafe-eval' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; base-uri 'none'; form-action 'self'; object-src 'none'; frame-ancestors 'none'; upgrade-insecure-requests" - "traefik.http.middlewares.securityHeaders.headers.referrerPolicy=no-referrer" - "traefik.http.middlewares.securityHeaders.headers.permissionsPolicy=accelerometer=(), autoplay=(), camera=(), cross-origin-isolated=(), display-capture=(), encrypted-media=(), fullscreen=(), geolocation=(), gyroscope=(), keyboard-map=(), magnetometer=(), microphone=(), midi=(), payment=(), picture-in-picture=(), publickey-credentials-get=(), screen-wake-lock=(), sync-xhr=(self), usb=(), web-share=(), xr-spatial-tracking=(), clipboard-read=(), clipboard-write=(), gamepad=(), hid=(), idle-detection=(), interest-cohort=(), serial=(), unload=()" ports: - "80:80" - "443:443" volumes: - /var/run/docker.sock:/var/run/docker.sock:ro - letsencrypt:/letsencrypt restart: unless-stopped networks: - traefik healthcheck: test: ["CMD", "traefik", "healthcheck", "--ping"] depends_on: watchtower: condition: service_healthy watchtower: image: containrrr/watchtower:latest command: --label-enable --interval 1800 --rolling-restart --cleanup --remove-volumes container_name: watchtower networks: - traefik volumes: - /var/run/docker.sock:/var/run/docker.sock - /home/troy/.docker/config.json:/config.json restart: unless-stopped healthcheck: test: ["CMD", "/watchtower", "--health-check"] personalsite: image: code.troylusty.com/troy/troylusty.com:latest container_name: personalsite labels: - "traefik.enable=true" - "traefik.http.routers.personalsite.rule=Host(`troylusty.com`)" - "traefik.http.routers.personalsite.entrypoints=websecure" - "traefik.http.routers.personalsite.tls.certresolver=myresolver" - "com.centurylinklabs.watchtower.enable=true" - "traefik.http.routers.personalsite.middlewares=securityHeaders" restart: unless-stopped networks: - traefik depends_on: traefik: condition: service_healthy zolapress: image: code.troylusty.com/troy/zolapress:latest container_name: zolapress profiles: - donotstart labels: - "traefik.enable=true" - "traefik.http.routers.zolapress.rule=Host(`edu.troylusty.com`)" - "traefik.http.routers.zolapress.entrypoints=websecure" - "traefik.http.routers.zolapress.tls.certresolver=myresolver" - "com.centurylinklabs.watchtower.enable=true" - "traefik.http.middlewares.auth.basicauth.users=troy:$$2y$$05$$fgVNzDsxXDq4co3aTh/OMOKZdLzUiM9XPEU5DXCivc9sYUZy/oq1W" - "traefik.http.routers.zolapress.middlewares=securityHeaders, auth" restart: unless-stopped networks: - traefik depends_on: traefik: condition: service_healthy unduck: image: code.troylusty.com/troy/unduck:latest container_name: unduck labels: - "traefik.enable=true" - "traefik.http.routers.unduck.rule=Host(`unduck.troylusty.com`)" - "traefik.http.routers.unduck.entrypoints=websecure" - "traefik.http.routers.unduck.tls.certresolver=myresolver" - "com.centurylinklabs.watchtower.enable=true" - "traefik.http.routers.unduck.middlewares=securityHeaders" restart: unless-stopped networks: - traefik depends_on: traefik: condition: service_healthy forgejo: image: codeberg.org/forgejo/forgejo:10 container_name: forgejo restart: unless-stopped networks: - traefik - forgejo labels: - "traefik.enable=true" - "traefik.http.routers.forgejo.rule=Host(`code.troylusty.com`)" - "traefik.http.routers.forgejo.entrypoints=websecure" - "traefik.http.routers.forgejo.tls.certresolver=myresolver" - "com.centurylinklabs.watchtower.enable=true" - "traefik.http.routers.forgejo.middlewares=securityHeaders" - "traefik.http.services.forgejo.loadbalancer.server.port=3000" - "traefik.docker.network=traefik" volumes: - ./forgejo:/data ports: - "2222:22" depends_on: traefik: condition: service_healthy forgejo-runner: image: code.forgejo.org/forgejo/runner:6.0.1 container_name: forgejo-runner user: 0:0 depends_on: forgejo: condition: service_started networks: - forgejo labels: - "com.centurylinklabs.watchtower.enable=true" volumes: - ./forgejo-runner:/data - ./forgejo-runner/config.yml:/data/config.yml - /var/run/docker.sock:/var/run/docker.sock restart: unless-stopped command: forgejo-runner -c /data/config.yml daemon networks: traefik: external: false name: traefik forgejo: external: false name: forgejo volumes: letsencrypt: ```